Merge "Populate individual systemserverclasspath_fragments' proto configs."
diff --git a/android/bazel.go b/android/bazel.go
index f56c24e..ef770bf 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -138,7 +138,6 @@
 		// e.g. ERROR: Analysis of target '@soong_injection//mixed_builds:buildroot' failed
 		"external/bazelbuild-rules_android":/* recursive = */ true,
 
-		"prebuilts/clang/host/linux-x86":/* recursive = */ false,
 		"prebuilts/sdk":/* recursive = */ false,
 		"prebuilts/sdk/tools":/* recursive = */ false,
 	}
@@ -155,6 +154,7 @@
 		"external/fmtlib":                 Bp2BuildDefaultTrueRecursively,
 		"external/arm-optimized-routines": Bp2BuildDefaultTrueRecursively,
 		"external/scudo":                  Bp2BuildDefaultTrueRecursively,
+		"prebuilts/clang/host/linux-x86":  Bp2BuildDefaultTrueRecursively,
 	}
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
diff --git a/android/variable.go b/android/variable.go
index 672576a..cf74933 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -467,7 +467,7 @@
 
 // ProductVariableProperties returns a ProductConfigProperties containing only the properties which
 // have been set for the module in the given context.
-func ProductVariableProperties(ctx ProductConfigContext) ProductConfigProperties {
+func ProductVariableProperties(ctx BaseMutatorContext) ProductConfigProperties {
 	module := ctx.Module()
 	moduleBase := module.base()
 
@@ -477,7 +477,28 @@
 		return productConfigProperties
 	}
 
-	variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName("Product_variables")
+	productVariableValues(moduleBase.variableProperties, "", &productConfigProperties)
+
+	for arch, targetProps := range moduleBase.GetArchProperties(ctx, moduleBase.variableProperties) {
+		// GetArchProperties is creating an instance of the requested type
+		// and productVariablesValues expects an interface, so no need to cast
+		productVariableValues(targetProps, arch.Name, &productConfigProperties)
+	}
+
+	for os, targetProps := range moduleBase.GetTargetProperties(ctx, moduleBase.variableProperties) {
+		// GetTargetProperties is creating an instance of the requested type
+		// and productVariablesValues expects an interface, so no need to cast
+		productVariableValues(targetProps, os.Name, &productConfigProperties)
+	}
+
+	return productConfigProperties
+}
+
+func productVariableValues(variableProps interface{}, suffix string, productConfigProperties *ProductConfigProperties) {
+	if suffix != "" {
+		suffix = "-" + suffix
+	}
+	variableValues := reflect.ValueOf(variableProps).Elem().FieldByName("Product_variables")
 	for i := 0; i < variableValues.NumField(); i++ {
 		variableValue := variableValues.Field(i)
 		// Check if any properties were set for the module
@@ -495,15 +516,13 @@
 
 			// e.g. Asflags, Cflags, Enabled, etc.
 			propertyName := variableValue.Type().Field(j).Name
-			productConfigProperties[propertyName] = append(productConfigProperties[propertyName],
+			(*productConfigProperties)[propertyName] = append((*productConfigProperties)[propertyName],
 				ProductConfigProperty{
-					ProductConfigVariable: productVariableName,
+					ProductConfigVariable: productVariableName + suffix,
 					Property:              property.Interface(),
 				})
 		}
 	}
-
-	return productConfigProperties
 }
 
 func VariableMutator(mctx BottomUpMutatorContext) {
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index b3f70cd..84cf9c4 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -62,6 +62,7 @@
 		apex {
 			name: "com.android.art",
 			key: "com.android.art.key",
+			bootclasspath_fragments: ["art-bootclasspath-fragment"],
  			java_libs: [
 				"baz",
 				"quuz",
@@ -100,32 +101,12 @@
 				"com.android.art",
 			],
 		}
-
-		bootclasspath_fragment {
-			name: "framework-bootclasspath-fragment",
-			image_name: "boot",
-		}
 `,
 	)
 
-	// Make sure that the framework-bootclasspath-fragment is using the correct configuration.
-	checkBootclasspathFragment(t, result, "framework-bootclasspath-fragment", "platform:foo,platform:bar", `
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.art
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.oat
-test_device/dex_bootjars/android/system/framework/arm/boot-foo.vdex
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.art
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.oat
-test_device/dex_bootjars/android/system/framework/arm/boot-bar.vdex
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.art
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.oat
-test_device/dex_bootjars/android/system/framework/arm64/boot-foo.vdex
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.art
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.oat
-test_device/dex_bootjars/android/system/framework/arm64/boot-bar.vdex
-`)
-
 	// Make sure that the art-bootclasspath-fragment is using the correct configuration.
-	checkBootclasspathFragment(t, result, "art-bootclasspath-fragment", "com.android.art:baz,com.android.art:quuz", `
+	checkBootclasspathFragment(t, result, "art-bootclasspath-fragment", "android_common_apex10000",
+		"com.android.art:baz,com.android.art:quuz", `
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.art
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.oat
 test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.vdex
@@ -141,10 +122,132 @@
 `)
 }
 
-func checkBootclasspathFragment(t *testing.T, result *android.TestResult, moduleName string, expectedConfiguredModules string, expectedBootclasspathFragmentFiles string) {
+func TestBootclasspathFragments_FragmentDependency(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootclasspathFragment,
+		// Configure some libraries in the art bootclasspath_fragment and platform_bootclasspath.
+		java.FixtureConfigureBootJars("com.android.art:baz", "com.android.art:quuz", "platform:foo", "platform:bar"),
+		prepareForTestWithArtApex,
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo", "baz"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		apex {
+			name: "com.android.art",
+			key: "com.android.art.key",
+			bootclasspath_fragments: ["art-bootclasspath-fragment"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "com.android.art.key",
+			public_key: "com.android.art.avbpubkey",
+			private_key: "com.android.art.pem",
+		}
+
+		java_sdk_library {
+			name: "baz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			system: {
+				enabled: true,
+			},
+			test: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "quuz",
+			apex_available: [
+				"com.android.art",
+			],
+			srcs: ["b.java"],
+			compile_dex: true,
+		}
+
+		bootclasspath_fragment {
+			name: "art-bootclasspath-fragment",
+			image_name: "art",
+			// Must match the "com.android.art:" entries passed to FixtureConfigureBootJars above.
+			contents: ["baz", "quuz"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		bootclasspath_fragment {
+			name: "other-bootclasspath-fragment",
+			contents: ["foo", "bar"],
+			fragments: [
+					{
+							apex: "com.android.art",
+							module: "art-bootclasspath-fragment",
+					},
+			],
+		}
+`,
+	)
+
+	checkSdkKindStubs := func(message string, info java.HiddenAPIInfo, kind android.SdkKind, expectedPaths ...string) {
+		t.Helper()
+		android.AssertPathsRelativeToTopEquals(t, fmt.Sprintf("%s %s", message, kind), expectedPaths, info.TransitiveStubDexJarsByKind[kind])
+	}
+
+	// Check stub dex paths exported by art.
+	artFragment := result.Module("art-bootclasspath-fragment", "android_common")
+	artInfo := result.ModuleProvider(artFragment, java.HiddenAPIInfoProvider).(java.HiddenAPIInfo)
+
+	bazPublicStubs := "out/soong/.intermediates/baz.stubs/android_common/dex/baz.stubs.jar"
+	bazSystemStubs := "out/soong/.intermediates/baz.stubs.system/android_common/dex/baz.stubs.system.jar"
+	bazTestStubs := "out/soong/.intermediates/baz.stubs.test/android_common/dex/baz.stubs.test.jar"
+
+	checkSdkKindStubs("art", artInfo, android.SdkPublic, bazPublicStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkSystem, bazSystemStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkTest, bazTestStubs)
+	checkSdkKindStubs("art", artInfo, android.SdkCorePlatform)
+
+	// Check stub dex paths exported by other.
+	otherFragment := result.Module("other-bootclasspath-fragment", "android_common")
+	otherInfo := result.ModuleProvider(otherFragment, java.HiddenAPIInfoProvider).(java.HiddenAPIInfo)
+
+	fooPublicStubs := "out/soong/.intermediates/foo.stubs/android_common/dex/foo.stubs.jar"
+	fooSystemStubs := "out/soong/.intermediates/foo.stubs.system/android_common/dex/foo.stubs.system.jar"
+
+	checkSdkKindStubs("other", otherInfo, android.SdkPublic, bazPublicStubs, fooPublicStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkSystem, bazSystemStubs, fooSystemStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkTest, bazTestStubs, fooSystemStubs)
+	checkSdkKindStubs("other", otherInfo, android.SdkCorePlatform)
+}
+
+func checkBootclasspathFragment(t *testing.T, result *android.TestResult, moduleName, variantName string, expectedConfiguredModules string, expectedBootclasspathFragmentFiles string) {
 	t.Helper()
 
-	bootclasspathFragment := result.ModuleForTests(moduleName, "android_common").Module().(*java.BootclasspathFragmentModule)
+	bootclasspathFragment := result.ModuleForTests(moduleName, variantName).Module().(*java.BootclasspathFragmentModule)
 
 	bootclasspathFragmentInfo := result.ModuleProvider(bootclasspathFragment, java.BootclasspathFragmentApexContentInfoProvider).(java.BootclasspathFragmentApexContentInfo)
 	modules := bootclasspathFragmentInfo.Modules()
diff --git a/bazel/properties.go b/bazel/properties.go
index 3e778bb..84dca7e 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -19,6 +19,7 @@
 	"path/filepath"
 	"regexp"
 	"sort"
+	"strings"
 )
 
 // BazelTargetModuleProperties contain properties and metadata used for
@@ -200,6 +201,10 @@
 	// config variable default key in an Android.bp file, although there's no
 	// integration with Soong config variables (yet).
 	CONDITIONS_DEFAULT = "conditions_default"
+
+	ConditionsDefaultSelectKey = "//conditions:default"
+
+	productVariableBazelPackage = "//build/bazel/product_variables"
 )
 
 var (
@@ -215,7 +220,7 @@
 		ARCH_ARM64:         "//build/bazel/platforms/arch:arm64",
 		ARCH_X86:           "//build/bazel/platforms/arch:x86",
 		ARCH_X86_64:        "//build/bazel/platforms/arch:x86_64",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of as arch select map.
+		CONDITIONS_DEFAULT: ConditionsDefaultSelectKey, // The default condition of as arch select map.
 	}
 
 	// A map of target operating systems to the Bazel label of the
@@ -227,7 +232,7 @@
 		OS_LINUX:           "//build/bazel/platforms/os:linux",
 		OS_LINUX_BIONIC:    "//build/bazel/platforms/os:linux_bionic",
 		OS_WINDOWS:         "//build/bazel/platforms/os:windows",
-		CONDITIONS_DEFAULT: "//conditions:default", // The default condition of an os select map.
+		CONDITIONS_DEFAULT: ConditionsDefaultSelectKey, // The default condition of an os select map.
 	}
 )
 
@@ -435,6 +440,10 @@
 	// are generated in a select statement and appended to the non-os specific
 	// label list Value.
 	OsValues stringListOsValues
+
+	// list of product-variable string list values. Optional. if used, each will generate a select
+	// statement appended to the label list Value.
+	ProductValues []ProductVariableValues
 }
 
 // MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
@@ -466,6 +475,18 @@
 	ConditionsDefault []string
 }
 
+// Product Variable values for StringListAttribute
+type ProductVariableValues struct {
+	ProductVariable string
+
+	Values []string
+}
+
+// SelectKey returns the appropriate select key for the receiving ProductVariableValues.
+func (p ProductVariableValues) SelectKey() string {
+	return fmt.Sprintf("%s:%s", productVariableBazelPackage, strings.ToLower(p.ProductVariable))
+}
+
 // HasConfigurableValues returns true if the attribute contains
 // architecture-specific string_list values.
 func (attrs StringListAttribute) HasConfigurableValues() bool {
@@ -480,7 +501,8 @@
 			return true
 		}
 	}
-	return false
+
+	return len(attrs.ProductValues) > 0
 }
 
 func (attrs *StringListAttribute) archValuePtrs() map[string]*[]string {
@@ -541,6 +563,12 @@
 	*v = value
 }
 
+func (attrs *StringListAttribute) SortedProductVariables() []ProductVariableValues {
+	vals := attrs.ProductValues[:]
+	sort.Slice(vals, func(i, j int) bool { return vals[i].ProductVariable < vals[j].ProductVariable })
+	return vals
+}
+
 // Append appends all values, including os and arch specific ones, from another
 // StringListAttribute to this StringListAttribute
 func (attrs *StringListAttribute) Append(other StringListAttribute) {
@@ -558,6 +586,21 @@
 		attrs.SetValueForOS(os, this)
 	}
 
+	productValues := make(map[string][]string, 0)
+	for _, pv := range attrs.ProductValues {
+		productValues[pv.ProductVariable] = pv.Values
+	}
+	for _, pv := range other.ProductValues {
+		productValues[pv.ProductVariable] = append(productValues[pv.ProductVariable], pv.Values...)
+	}
+	attrs.ProductValues = make([]ProductVariableValues, 0, len(productValues))
+	for pv, vals := range productValues {
+		attrs.ProductValues = append(attrs.ProductValues, ProductVariableValues{
+			ProductVariable: pv,
+			Values:          vals,
+		})
+	}
+
 	attrs.Value = append(attrs.Value, other.Value...)
 }
 
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index bddc524..7a73e18 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -19,6 +19,7 @@
 	"android/soong/bazel"
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -34,6 +35,7 @@
 	content         string
 	ruleClass       string
 	bzlLoadLocation string
+	handcrafted     bool
 }
 
 // IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
@@ -45,12 +47,47 @@
 // BazelTargets is a typedef for a slice of BazelTarget objects.
 type BazelTargets []BazelTarget
 
+// HasHandcraftedTargetsreturns true if a set of bazel targets contain
+// handcrafted ones.
+func (targets BazelTargets) hasHandcraftedTargets() bool {
+	for _, target := range targets {
+		if target.handcrafted {
+			return true
+		}
+	}
+	return false
+}
+
+// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
+func (targets BazelTargets) sort() {
+	sort.Slice(targets, func(i, j int) bool {
+		if targets[i].handcrafted != targets[j].handcrafted {
+			// Handcrafted targets will be generated after the bp2build generated targets.
+			return targets[j].handcrafted
+		}
+		// This will cover all bp2build generated targets.
+		return targets[i].name < targets[j].name
+	})
+}
+
 // String returns the string representation of BazelTargets, without load
 // statements (use LoadStatements for that), since the targets are usually not
 // adjacent to the load statements at the top of the BUILD file.
 func (targets BazelTargets) String() string {
 	var res string
 	for i, target := range targets {
+		// There is only at most 1 handcrafted "target", because its contents
+		// represent the entire BUILD file content from the tree. See
+		// build_conversion.go#getHandcraftedBuildContent for more information.
+		//
+		// Add a header to make it easy to debug where the handcrafted targets
+		// are in a generated BUILD file.
+		if target.handcrafted {
+			res += "# -----------------------------\n"
+			res += "# Section: Handcrafted targets. \n"
+			res += "# -----------------------------\n\n"
+		}
+
 		res += target.content
 		if i != len(targets)-1 {
 			res += "\n\n"
@@ -267,7 +304,8 @@
 	}
 	// TODO(b/181575318): once this is more targeted, we need to include name, rule class, etc
 	return BazelTarget{
-		content: c,
+		content:     c,
+		handcrafted: true,
 	}, nil
 }
 
@@ -294,6 +332,7 @@
 			targetName,
 			attributes,
 		),
+		handcrafted: false,
 	}
 }
 
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 71660a8..b1c342c 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -1452,53 +1452,61 @@
 
 	dir := "."
 	for _, testCase := range testCases {
-		fs := make(map[string][]byte)
-		toParse := []string{
-			"Android.bp",
-		}
-		for f, content := range testCase.fs {
-			if strings.HasSuffix(f, "Android.bp") {
-				toParse = append(toParse, f)
+		t.Run(testCase.description, func(t *testing.T) {
+			fs := make(map[string][]byte)
+			toParse := []string{
+				"Android.bp",
 			}
-			fs[f] = []byte(content)
-		}
-		config := android.TestConfig(buildDir, nil, testCase.bp, fs)
-		ctx := android.NewTestContext(config)
-		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
-		for _, m := range testCase.depsMutators {
-			ctx.DepsBp2BuildMutators(m)
-		}
-		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
-		ctx.RegisterForBazelConversion()
+			for f, content := range testCase.fs {
+				if strings.HasSuffix(f, "Android.bp") {
+					toParse = append(toParse, f)
+				}
+				fs[f] = []byte(content)
+			}
+			config := android.TestConfig(buildDir, nil, testCase.bp, fs)
+			ctx := android.NewTestContext(config)
+			ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+			for _, m := range testCase.depsMutators {
+				ctx.DepsBp2BuildMutators(m)
+			}
+			ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+			ctx.RegisterForBazelConversion()
 
-		_, errs := ctx.ParseFileList(dir, toParse)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
-		_, errs = ctx.ResolveDependencies(config)
-		if errored(t, testCase.description, errs) {
-			continue
-		}
+			_, errs := ctx.ParseFileList(dir, toParse)
+			if errored(t, testCase.description, errs) {
+				return
+			}
+			_, errs = ctx.ResolveDependencies(config)
+			if errored(t, testCase.description, errs) {
+				return
+			}
 
-		checkDir := dir
-		if testCase.dir != "" {
-			checkDir = testCase.dir
-		}
-		bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
-		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
-			t.Errorf("%s: Expected %d bazel target, got %d\n%s", testCase.description, expectedCount, actualCount, bazelTargets)
-		} else {
+			checkDir := dir
+			if testCase.dir != "" {
+				checkDir = testCase.dir
+			}
+			bazelTargets := generateBazelTargetsForDir(NewCodegenContext(config, *ctx.Context, Bp2Build), checkDir)
+			bazelTargets.sort()
+			actualCount := len(bazelTargets)
+			expectedCount := len(testCase.expectedBazelTargets)
+			if actualCount != expectedCount {
+				t.Errorf("Expected %d bazel target, got %d\n%s", expectedCount, actualCount, bazelTargets)
+			}
+			if !strings.Contains(bazelTargets.String(), "Section: Handcrafted targets. ") {
+				t.Errorf("Expected string representation of bazelTargets to contain handcrafted section header.")
+			}
 			for i, target := range bazelTargets {
-				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+				actualContent := target.content
+				expectedContent := testCase.expectedBazelTargets[i]
+				if expectedContent != actualContent {
 					t.Errorf(
-						"%s: Expected generated Bazel target to be '%s', got '%s'",
-						testCase.description,
-						w,
-						g,
+						"Expected generated Bazel target to be '%s', got '%s'",
+						expectedContent,
+						actualContent,
 					)
 				}
 			}
-		}
+		})
 	}
 }
 
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 62084a5..229b1c2 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -1156,3 +1156,120 @@
 )`},
 	})
 }
+
+func TestCcLibraryStaticProductVariableSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+      malloc_zero_contents: {
+        cflags: ["-Wmalloc_zero_contents"],
+      },
+      binder32bit: {
+        cflags: ["-Wbinder32bit"],
+      },
+    },
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ] + select({
+        "//build/bazel/product_variables:binder32bit": ["-Wbinder32bit"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_zero_contents": ["-Wmalloc_zero_contents"],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+    srcs = ["common.c"],
+)`},
+	})
+}
+
+func TestCcLibraryStaticProductVariableArchSpecificSelects(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static arch-specific product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      malloc_not_svelte: {
+        cflags: ["-Wmalloc_not_svelte"],
+      },
+    },
+		arch: {
+				arm64: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Warm64_malloc_not_svelte"],
+								},
+						},
+				},
+		},
+		multilib: {
+				lib32: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Wlib32_malloc_not_svelte"],
+								},
+						},
+				},
+		},
+		target: {
+				android: {
+						product_variables: {
+								malloc_not_svelte: {
+										cflags: ["-Wandroid_malloc_not_svelte"],
+								},
+						},
+				}
+		},
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ] + select({
+        "//build/bazel/product_variables:malloc_not_svelte": ["-Wmalloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-android": ["-Wandroid_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-arm64": ["-Warm64_malloc_not_svelte"],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/product_variables:malloc_not_svelte-x86": ["-Wlib32_malloc_not_svelte"],
+        "//conditions:default": [],
+    }),
+    linkstatic = True,
+    srcs = ["common.c"],
+)`},
+	})
+}
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index b69135b..d4eeb7c 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -208,7 +208,10 @@
 `,
 		expectedBazelTargets: []string{`cc_object(
     name = "foo",
-    asflags = ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
+    asflags = select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
+        "//conditions:default": [],
+    }),
     copts = ["-fno-addrsig"],
 )`,
 		},
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 2b8f6cc..9869c5d 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -11,26 +11,42 @@
 
 type selects map[string]reflect.Value
 
-func getStringListValues(list bazel.StringListAttribute) (reflect.Value, selects, selects) {
+func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value)
 	if !list.HasConfigurableValues() {
-		return value, nil, nil
+		return value, []selects{}
 	}
 
+	selectValues := make([]selects, 0)
 	archSelects := map[string]reflect.Value{}
 	for arch, selectKey := range bazel.PlatformArchMap {
 		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch))
 	}
+	if len(archSelects) > 0 {
+		selectValues = append(selectValues, archSelects)
+	}
 
 	osSelects := map[string]reflect.Value{}
 	for os, selectKey := range bazel.PlatformOsMap {
 		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os))
 	}
+	if len(osSelects) > 0 {
+		selectValues = append(selectValues, osSelects)
+	}
 
-	return value, archSelects, osSelects
+	for _, pv := range list.SortedProductVariables() {
+		s := make(selects)
+		if len(pv.Values) > 0 {
+			s[pv.SelectKey()] = reflect.ValueOf(pv.Values)
+			s[bazel.ConditionsDefaultSelectKey] = reflect.ValueOf([]string{})
+			selectValues = append(selectValues, s)
+		}
+	}
+
+	return value, selectValues
 }
 
-func getLabelValue(label bazel.LabelAttribute) (reflect.Value, selects, selects) {
+func getLabelValue(label bazel.LabelAttribute) (reflect.Value, []selects) {
 	var value reflect.Value
 	var archSelects selects
 
@@ -43,13 +59,13 @@
 		value = reflect.ValueOf(label.Value)
 	}
 
-	return value, archSelects, nil
+	return value, []selects{archSelects}
 }
 
-func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, selects, selects) {
+func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, []selects) {
 	value := reflect.ValueOf(list.Value.Includes)
 	if !list.HasConfigurableValues() {
-		return value, nil, nil
+		return value, []selects{}
 	}
 
 	archSelects := map[string]reflect.Value{}
@@ -62,29 +78,30 @@
 		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os).Includes)
 	}
 
-	return value, archSelects, osSelects
+	return value, []selects{archSelects, osSelects}
 }
 
 // prettyPrintAttribute converts an Attribute to its Bazel syntax. May contain
 // select statements.
 func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
 	var value reflect.Value
-	var archSelects, osSelects selects
+	var configurableAttrs []selects
 	var defaultSelectValue string
 	switch list := v.(type) {
 	case bazel.StringListAttribute:
-		value, archSelects, osSelects = getStringListValues(list)
+		value, configurableAttrs = getStringListValues(list)
 		defaultSelectValue = "[]"
 	case bazel.LabelListAttribute:
-		value, archSelects, osSelects = getLabelListValues(list)
+		value, configurableAttrs = getLabelListValues(list)
 		defaultSelectValue = "[]"
 	case bazel.LabelAttribute:
-		value, archSelects, osSelects = getLabelValue(list)
+		value, configurableAttrs = getLabelValue(list)
 		defaultSelectValue = "None"
 	default:
 		return "", fmt.Errorf("Not a supported Bazel attribute type: %s", v)
 	}
 
+	var err error
 	ret := ""
 	if value.Kind() != reflect.Invalid {
 		s, err := prettyPrint(value, indent)
@@ -108,13 +125,14 @@
 		return s, nil
 	}
 
-	ret, err := appendSelects(archSelects, defaultSelectValue, ret)
-	if err != nil {
-		return "", err
+	for _, configurableAttr := range configurableAttrs {
+		ret, err = appendSelects(configurableAttr, defaultSelectValue, ret)
+		if err != nil {
+			return "", err
+		}
 	}
 
-	ret, err = appendSelects(osSelects, defaultSelectValue, ret)
-	return ret, err
+	return ret, nil
 }
 
 // prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
@@ -125,11 +143,10 @@
 	}
 
 	// addConditionsDefault := false
-	conditionsDefaultKey := bazel.PlatformArchMap[bazel.CONDITIONS_DEFAULT]
 
 	var selects string
 	for _, selectKey := range android.SortedStringKeys(selectMap) {
-		if selectKey == conditionsDefaultKey {
+		if selectKey == bazel.ConditionsDefaultSelectKey {
 			// Handle default condition later.
 			continue
 		}
@@ -159,14 +176,14 @@
 	ret += selects
 
 	// Handle the default condition
-	s, err := prettyPrintSelectEntry(selectMap[conditionsDefaultKey], conditionsDefaultKey, indent)
+	s, err := prettyPrintSelectEntry(selectMap[bazel.ConditionsDefaultSelectKey], bazel.ConditionsDefaultSelectKey, indent)
 	if err != nil {
 		return "", err
 	}
 	if s == "" {
 		// Print an explicit empty list (the default value) even if the value is
 		// empty, to avoid errors about not finding a configuration that matches.
-		ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)
+		ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), bazel.ConditionsDefaultSelectKey, defaultValue)
 	} else {
 		// Print the custom default value.
 		ret += s
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 101ad3d..bced4c1 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -5,7 +5,6 @@
 	"android/soong/cc/config"
 	"fmt"
 	"reflect"
-	"sort"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -64,22 +63,28 @@
 			continue
 		}
 		targets := buildToTargets[dir]
-		sort.Slice(targets, func(i, j int) bool {
-			// this will cover all bp2build generated targets
-			if targets[i].name < targets[j].name {
-				return true
-			}
-			// give a strict ordering to content from hand-crafted targets
-			return targets[i].content < targets[j].content
-		})
-		content := soongModuleLoad
+		targets.sort()
+
+		var content string
 		if mode == Bp2Build {
-			content = `# This file was automatically generated by bp2build for the Bazel migration project.
-# Feel free to edit or test it, but do *not* check it into your version control system.`
-			content += "\n\n"
-			content += "package(default_visibility = [\"//visibility:public\"])"
-			content += "\n\n"
+			content = `# READ THIS FIRST:
+# This file was automatically generated by bp2build for the Bazel migration project.
+# Feel free to edit or test it, but do *not* check it into your version control system.
+`
+			if targets.hasHandcraftedTargets() {
+				// For BUILD files with both handcrafted and generated targets,
+				// don't hardcode actual content, like package() declarations.
+				// Leave that responsibility to the checked-in BUILD file
+				// instead.
+				content += `# This file contains generated targets and handcrafted targets that are manually managed in the source tree.`
+			} else {
+				// For fully-generated BUILD files, hardcode the default visibility.
+				content += "package(default_visibility = [\"//visibility:public\"])"
+			}
+			content += "\n"
 			content += targets.LoadStatements()
+		} else if mode == QueryView {
+			content = soongModuleLoad
 		}
 		if content != "" {
 			// If there are load statements, add a couple of newlines.
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 95a3fe1..0c827c5 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -318,6 +318,21 @@
 		}
 	}
 
+	productVariableProps := android.ProductVariableProperties(ctx)
+	if props, exists := productVariableProps["Cflags"]; exists {
+		for _, prop := range props {
+			flags, ok := prop.Property.([]string)
+			if !ok {
+				ctx.ModuleErrorf("Could not convert product variable cflag property")
+			}
+			newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
+			copts.ProductValues = append(copts.ProductValues, bazel.ProductVariableValues{
+				ProductVariable: prop.ProductConfigVariable,
+				Values:          newFlags,
+			})
+		}
+	}
+
 	return compilerAttributes{
 		srcs:  srcs,
 		copts: copts,
diff --git a/cc/cc_test.go b/cc/cc_test.go
index d82619a..5acafbe 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -554,6 +554,13 @@
 			}
 		}
 
+		cc_library {
+			name: "libclang_rt.hwasan-llndk",
+			llndk: {
+				symbol_file: "libclang_rt.hwasan.map.txt",
+			}
+		}
+
 		cc_library_headers {
 			name: "libllndk_headers",
 			llndk: {
@@ -661,7 +668,7 @@
 		"VNDK-product: libvndk_product.so",
 		"VNDK-product: libvndk_sp_product_private-x.so",
 	})
-	checkVndkLibrariesOutput(t, ctx, "llndk.libraries.txt", []string{"libc.so", "libdl.so", "libft2.so", "libllndk.so", "libm.so"})
+	checkVndkLibrariesOutput(t, ctx, "llndk.libraries.txt", []string{"libc.so", "libclang_rt.hwasan-llndk.so", "libdl.so", "libft2.so", "libllndk.so", "libm.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndkcore.libraries.txt", []string{"libvndk-private.so", "libvndk.so", "libvndk_product.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndksp.libraries.txt", []string{"libc++.so", "libvndk_sp-x.so", "libvndk_sp_private-x.so", "libvndk_sp_product_private-x.so"})
 	checkVndkLibrariesOutput(t, ctx, "vndkprivate.libraries.txt", []string{"libft2.so", "libvndk-private.so", "libvndk_sp_private-x.so", "libvndk_sp_product_private-x.so"})
diff --git a/cc/object.go b/cc/object.go
index d8f1aba..704cb69 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -116,7 +116,7 @@
 	Hdrs    bazel.LabelListAttribute
 	Deps    bazel.LabelListAttribute
 	Copts   bazel.StringListAttribute
-	Asflags []string
+	Asflags bazel.StringListAttribute
 }
 
 type bazelObject struct {
@@ -157,7 +157,7 @@
 
 	// Set arch-specific configurable attributes
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	var asFlags []string
+	var asFlags bazel.StringListAttribute
 
 	var deps bazel.LabelListAttribute
 	for _, props := range m.linker.linkerProps() {
@@ -176,10 +176,11 @@
 				ctx.ModuleErrorf("Could not convert product variable asflag property")
 				return
 			}
-			// TODO(b/183595873) handle other product variable usages -- as selects?
-			if newFlags, subbed := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable); subbed {
-				asFlags = append(asFlags, newFlags...)
-			}
+			newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
+			asFlags.ProductValues = append(asFlags.ProductValues, bazel.ProductVariableValues{
+				ProductVariable: prop.ProductConfigVariable,
+				Values:          newFlags,
+			})
 		}
 	}
 	// TODO(b/183595872) warn/error if we're not handling product variables
diff --git a/cc/vndk.go b/cc/vndk.go
index 0254edc..6a56c34 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -234,7 +234,6 @@
 
 var (
 	llndkLibraries                = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsLLNDK && !m.Header() })
-	llndkLibrariesWithoutHWASAN   = vndkModuleListRemover(llndkLibraries, "libclang_rt.hwasan-")
 	vndkSPLibraries               = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKSP })
 	vndkCoreLibraries             = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKCore })
 	vndkPrivateLibraries          = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKPrivate })
@@ -419,10 +418,6 @@
 }
 
 func RegisterVndkLibraryTxtTypes(ctx android.RegistrationContext) {
-	// Make uses LLNDK_LIBRARIES to determine which libraries to install.
-	// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
-	// Therefore, by removing the library here, we cause it to only be installed if libc
-	// depends on it.
 	ctx.RegisterSingletonModuleType("llndk_libraries_txt", llndkLibrariesTxtFactory)
 	ctx.RegisterSingletonModuleType("vndksp_libraries_txt", vndkSPLibrariesTxtFactory)
 	ctx.RegisterSingletonModuleType("vndkcore_libraries_txt", vndkCoreLibrariesTxtFactory)
@@ -434,8 +429,9 @@
 type vndkLibrariesTxt struct {
 	android.SingletonModuleBase
 
-	lister      moduleListerFunc
-	makeVarName string
+	lister               moduleListerFunc
+	makeVarName          string
+	filterOutFromMakeVar string
 
 	properties VndkLibrariesTxtProperties
 
@@ -454,8 +450,12 @@
 // llndk_libraries_txt is a singleton module whose content is a list of LLNDK libraries
 // generated by Soong but can be referenced by other modules.
 // For example, apex_vndk can depend on these files as prebuilt.
+// Make uses LLNDK_LIBRARIES to determine which libraries to install.
+// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
+// Therefore, by removing the library here, we cause it to only be installed if libc
+// depends on it.
 func llndkLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(llndkLibrariesWithoutHWASAN, "LLNDK_LIBRARIES")
+	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "LLNDK_LIBRARIES", "libclang_rt.hwasan-")
 }
 
 // vndksp_libraries_txt is a singleton module whose content is a list of VNDKSP libraries
@@ -493,16 +493,21 @@
 	return newVndkLibrariesTxt(vndkUsingCoreVariantLibraries, "VNDK_USING_CORE_VARIANT_LIBRARIES")
 }
 
-func newVndkLibrariesTxt(lister moduleListerFunc, makeVarName string) android.SingletonModule {
+func newVndkLibrariesWithMakeVarFilter(lister moduleListerFunc, makeVarName string, filter string) android.SingletonModule {
 	m := &vndkLibrariesTxt{
-		lister:      lister,
-		makeVarName: makeVarName,
+		lister:               lister,
+		makeVarName:          makeVarName,
+		filterOutFromMakeVar: filter,
 	}
 	m.AddProperties(&m.properties)
 	android.InitAndroidModule(m)
 	return m
 }
 
+func newVndkLibrariesTxt(lister moduleListerFunc, makeVarName string) android.SingletonModule {
+	return newVndkLibrariesWithMakeVarFilter(lister, makeVarName, "")
+}
+
 func insertVndkVersion(filename string, vndkVersion string) string {
 	if index := strings.LastIndex(filename, "."); index != -1 {
 		return filename[:index] + "." + vndkVersion + filename[index:]
@@ -542,8 +547,21 @@
 }
 
 func (txt *vndkLibrariesTxt) MakeVars(ctx android.MakeVarsContext) {
-	ctx.Strict(txt.makeVarName, strings.Join(txt.moduleNames, " "))
-
+	filter := func(modules []string, prefix string) []string {
+		if prefix == "" {
+			return modules
+		}
+		var result []string
+		for _, module := range modules {
+			if strings.HasPrefix(module, prefix) {
+				continue
+			} else {
+				result = append(result, module)
+			}
+		}
+		return result
+	}
+	ctx.Strict(txt.makeVarName, strings.Join(filter(txt.moduleNames, txt.filterOutFromMakeVar), " "))
 }
 
 // PrebuiltEtcModule interface
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 70c8856..7abb67f 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -175,6 +175,9 @@
 	writeFakeNinjaFile(extraNinjaDeps, configuration.BuildDir())
 }
 
+// doChosenActivity runs Soong for a specific activity, like bp2build, queryview
+// or the actual Soong build for the build.ninja file. Returns the top level
+// output file of the specific activity.
 func doChosenActivity(configuration android.Config, extraNinjaDeps []string) string {
 	bazelConversionRequested := bp2buildMarker != ""
 	mixedModeBuild := configuration.BazelContext.BazelEnabled()
@@ -187,11 +190,7 @@
 		// Run the alternate pipeline of bp2build mutators and singleton to convert
 		// Blueprint to BUILD files before everything else.
 		runBp2Build(configuration, extraNinjaDeps)
-		if bp2buildMarker != "" {
-			return bp2buildMarker
-		} else {
-			return bootstrap.CmdlineArgs.OutFile
-		}
+		return bp2buildMarker
 	}
 
 	ctx := newContext(configuration, prepareBuildActions)
@@ -327,13 +326,13 @@
 
 	ninjaFileName := "build.ninja"
 	ninjaFile := shared.JoinPath(topDir, buildDir, ninjaFileName)
-	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName)
+	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName+".d")
 	// A workaround to create the 'nothing' ninja target so `m nothing` works,
 	// since bp2build runs without Kati, and the 'nothing' target is declared in
 	// a Makefile.
 	ioutil.WriteFile(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
 	ioutil.WriteFile(ninjaFileD,
-		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
+		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFile, extraNinjaDepsString)),
 		0666)
 }
 
@@ -520,9 +519,14 @@
 		os.Exit(1)
 	}
 
-	if bp2buildMarker != "" {
-		touch(shared.JoinPath(topDir, bp2buildMarker))
-	} else {
-		writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
-	}
+	// Create an empty bp2build marker file.
+	touch(shared.JoinPath(topDir, bp2buildMarker))
+
+	// bp2build *always* writes a fake Ninja file containing just the nothing
+	// phony target if it ever re-runs. This allows bp2build to exit early with
+	// GENERATE_BAZEL_FILES=1 m nothing.
+	//
+	// If bp2build is invoked as part of an integrated mixed build, the fake
+	// build.ninja file will be rewritten later into the real file anyway.
+	writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
 }
diff --git a/java/Android.bp b/java/Android.bp
index 623a6c5..680f3a1 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -45,6 +45,7 @@
         "genrule.go",
         "hiddenapi.go",
         "hiddenapi_modular.go",
+        "hiddenapi_monolithic.go",
         "hiddenapi_singleton.go",
         "jacoco.go",
         "java.go",
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 634959a..eddcc83 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -235,12 +235,3 @@
 	m[android.SdkCorePlatform] = p.Core_platform_api.Stub_libs
 	return m
 }
-
-// bootclasspathApiInfo contains paths resolved from BootclasspathAPIProperties
-type bootclasspathApiInfo struct {
-	// stubJarsByKind maps from the android.SdkKind to the paths containing dex stub jars for each
-	// kind.
-	stubJarsByKind map[android.SdkKind]android.Paths
-}
-
-var bootclasspathApiInfoProvider = blueprint.NewProvider(bootclasspathApiInfo{})
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index e151d63..188d362 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -133,10 +133,12 @@
 type commonBootclasspathFragment interface {
 	// produceHiddenAPIAllFlagsFile produces the all-flags.csv and intermediate files.
 	//
-	// Updates the supplied flagFileInfo with the paths to the generated files set.
-	produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, stubJarsByKind map[android.SdkKind]android.Paths, flagFileInfo *hiddenAPIFlagFileInfo)
+	// Updates the supplied hiddenAPIInfo with the paths to the generated files set.
+	produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, input HiddenAPIFlagInput) *HiddenAPIFlagOutput
 }
 
+var _ commonBootclasspathFragment = (*BootclasspathFragmentModule)(nil)
+
 func bootclasspathFragmentFactory() android.Module {
 	m := &BootclasspathFragmentModule{}
 	m.AddProperties(&m.properties)
@@ -166,61 +168,70 @@
 // necessary.
 func bootclasspathFragmentInitContentsFromImage(ctx android.EarlyModuleContext, m *BootclasspathFragmentModule) {
 	contents := m.properties.Contents
-	if m.properties.Image_name == nil && len(contents) == 0 {
-		ctx.ModuleErrorf(`neither of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
+	if len(contents) == 0 {
+		ctx.PropertyErrorf("contents", "required property is missing")
+		return
+	}
+
+	if m.properties.Image_name == nil {
+		// Nothing to do.
+		return
 	}
 
 	imageName := proptools.String(m.properties.Image_name)
-	if imageName == "art" {
-		// TODO(b/177892522): Prebuilts (versioned or not) should not use the image_name property.
-		if android.IsModuleInVersionedSdk(m) {
-			// The module is a versioned prebuilt so ignore it. This is done for a couple of reasons:
-			// 1. There is no way to use this at the moment so ignoring it is safe.
-			// 2. Attempting to initialize the contents property from the configuration will end up having
-			//    the versioned prebuilt depending on the unversioned prebuilt. That will cause problems
-			//    as the unversioned prebuilt could end up with an APEX variant created for the source
-			//    APEX which will prevent it from having an APEX variant for the prebuilt APEX which in
-			//    turn will prevent it from accessing the dex implementation jar from that which will
-			//    break hidden API processing, amongst others.
-			return
-		}
-
-		// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
-		// too early in the Soong processing for that to work.
-		global := dexpreopt.GetGlobalConfig(ctx)
-		modules := global.ArtApexJars
-
-		// Make sure that the apex specified in the configuration is consistent and is one for which
-		// this boot image is available.
-		commonApex := ""
-		for i := 0; i < modules.Len(); i++ {
-			apex := modules.Apex(i)
-			jar := modules.Jar(i)
-			if apex == "platform" {
-				ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
-				continue
-			}
-			if !m.AvailableFor(apex) {
-				ctx.ModuleErrorf("ArtApexJars configuration incompatible with this module, ArtApexJars expects this to be in apex %q but this is only in apexes %q",
-					apex, m.ApexAvailable())
-				continue
-			}
-			if commonApex == "" {
-				commonApex = apex
-			} else if commonApex != apex {
-				ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
-					commonApex, apex)
-			}
-		}
-
-		if len(contents) != 0 {
-			// Nothing to do.
-			return
-		}
-
-		// Store the jars in the Contents property so that they can be used to add dependencies.
-		m.properties.Contents = modules.CopyOfJars()
+	if imageName != "art" {
+		ctx.PropertyErrorf("image_name", `unknown image name %q, expected "art"`, imageName)
+		return
 	}
+
+	// TODO(b/177892522): Prebuilts (versioned or not) should not use the image_name property.
+	if android.IsModuleInVersionedSdk(m) {
+		// The module is a versioned prebuilt so ignore it. This is done for a couple of reasons:
+		// 1. There is no way to use this at the moment so ignoring it is safe.
+		// 2. Attempting to initialize the contents property from the configuration will end up having
+		//    the versioned prebuilt depending on the unversioned prebuilt. That will cause problems
+		//    as the unversioned prebuilt could end up with an APEX variant created for the source
+		//    APEX which will prevent it from having an APEX variant for the prebuilt APEX which in
+		//    turn will prevent it from accessing the dex implementation jar from that which will
+		//    break hidden API processing, amongst others.
+		return
+	}
+
+	// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
+	// too early in the Soong processing for that to work.
+	global := dexpreopt.GetGlobalConfig(ctx)
+	modules := global.ArtApexJars
+
+	// Make sure that the apex specified in the configuration is consistent and is one for which
+	// this boot image is available.
+	commonApex := ""
+	for i := 0; i < modules.Len(); i++ {
+		apex := modules.Apex(i)
+		jar := modules.Jar(i)
+		if apex == "platform" {
+			ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
+			continue
+		}
+		if !m.AvailableFor(apex) {
+			ctx.ModuleErrorf("ArtApexJars configuration incompatible with this module, ArtApexJars expects this to be in apex %q but this is only in apexes %q",
+				apex, m.ApexAvailable())
+			continue
+		}
+		if commonApex == "" {
+			commonApex = apex
+		} else if commonApex != apex {
+			ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
+				commonApex, apex)
+		}
+	}
+
+	if len(contents) != 0 {
+		// Nothing to do.
+		return
+	}
+
+	// Store the jars in the Contents property so that they can be used to add dependencies.
+	m.properties.Contents = modules.CopyOfJars()
 }
 
 // bootclasspathImageNameContentsConsistencyCheck checks that the configuration that applies to this
@@ -268,11 +279,12 @@
 // BootclasspathFragmentApexContentInfo contains the bootclasspath_fragments contributions to the
 // apex contents.
 type BootclasspathFragmentApexContentInfo struct {
-	// The image config, internal to this module (and the dex_bootjars singleton).
-	//
-	// Will be nil if the BootclasspathFragmentApexContentInfo has not been provided for a specific module. That can occur
-	// when SkipDexpreoptBootJars(ctx) returns true.
-	imageConfig *bootImageConfig
+	// The configured modules, will be empty if this is from a bootclasspath_fragment that does not
+	// set image_name: "art".
+	modules android.ConfiguredJarList
+
+	// Map from arch type to the boot image files.
+	bootImageFilesByArch map[android.ArchType]android.OutputPaths
 
 	// Map from the name of the context module (as returned by Name()) to the hidden API encoded dex
 	// jar path.
@@ -280,24 +292,14 @@
 }
 
 func (i BootclasspathFragmentApexContentInfo) Modules() android.ConfiguredJarList {
-	return i.imageConfig.modules
+	return i.modules
 }
 
 // Get a map from ArchType to the associated boot image's contents for Android.
 //
 // Extension boot images only return their own files, not the files of the boot images they extend.
 func (i BootclasspathFragmentApexContentInfo) AndroidBootImageFilesByArchType() map[android.ArchType]android.OutputPaths {
-	files := map[android.ArchType]android.OutputPaths{}
-	if i.imageConfig != nil {
-		for _, variant := range i.imageConfig.variants {
-			// We also generate boot images for host (for testing), but we don't need those in the apex.
-			// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
-			if variant.target.Os == android.Android {
-				files[variant.target.Arch.ArchType] = variant.imagesDeps
-			}
-		}
-	}
-	return files
+	return i.bootImageFilesByArch
 }
 
 // DexBootJarPathForContentModule returns the path to the dex boot jar for specified module.
@@ -365,6 +367,11 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
+func (b *BootclasspathFragmentModule) BootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
+	// Add dependencies on all the fragments.
+	b.properties.BootclasspathFragmentsDepsProperties.addDependenciesOntoFragments(ctx)
+}
+
 func (b *BootclasspathFragmentModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Only perform a consistency check if this module is the active module. That will prevent an
 	// unused prebuilt that was created without instrumentation from breaking an instrumentation
@@ -385,8 +392,10 @@
 		}
 	})
 
+	fragments := gatherApexModulePairDepsWithTag(ctx, bootclasspathFragmentDepTag)
+
 	// Perform hidden API processing.
-	hiddenAPIInfo := b.generateHiddenAPIBuildActions(ctx, contents)
+	hiddenAPIFlagOutput := b.generateHiddenAPIBuildActions(ctx, contents, fragments)
 
 	// Verify that the image_name specified on a bootclasspath_fragment is valid even if this is a
 	// prebuilt which will not use the image config.
@@ -395,28 +404,41 @@
 	// A prebuilt fragment cannot contribute to the apex.
 	if !android.IsModulePrebuilt(ctx.Module()) {
 		// Provide the apex content info.
-		b.provideApexContentInfo(ctx, imageConfig, contents, hiddenAPIInfo)
+		b.provideApexContentInfo(ctx, imageConfig, contents, hiddenAPIFlagOutput)
 	}
 }
 
 // provideApexContentInfo creates, initializes and stores the apex content info for use by other
 // modules.
-func (b *BootclasspathFragmentModule) provideApexContentInfo(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module, hiddenAPIInfo *hiddenAPIFlagFileInfo) {
+func (b *BootclasspathFragmentModule) provideApexContentInfo(ctx android.ModuleContext, imageConfig *bootImageConfig, contents []android.Module, hiddenAPIFlagOutput *HiddenAPIFlagOutput) {
 	// Construct the apex content info from the config.
-	info := BootclasspathFragmentApexContentInfo{
-		imageConfig: imageConfig,
-	}
+	info := BootclasspathFragmentApexContentInfo{}
 
 	// Populate the apex content info with paths to the dex jars.
-	b.populateApexContentInfoDexJars(ctx, &info, contents, hiddenAPIInfo)
+	b.populateApexContentInfoDexJars(ctx, &info, contents, hiddenAPIFlagOutput)
 
-	if !SkipDexpreoptBootJars(ctx) {
-		// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
-		// GenerateSingletonBuildActions method as it cannot create it for itself.
-		dexpreopt.GetGlobalSoongConfig(ctx)
+	if imageConfig != nil {
+		info.modules = imageConfig.modules
 
-		// Only generate the boot image if the configuration does not skip it.
-		b.generateBootImageBuildActions(ctx, contents)
+		if !SkipDexpreoptBootJars(ctx) {
+			// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
+			// GenerateSingletonBuildActions method as it cannot create it for itself.
+			dexpreopt.GetGlobalSoongConfig(ctx)
+
+			// Only generate the boot image if the configuration does not skip it.
+			if b.generateBootImageBuildActions(ctx, contents, imageConfig) {
+				// Allow the apex to access the boot image files.
+				files := map[android.ArchType]android.OutputPaths{}
+				for _, variant := range imageConfig.variants {
+					// We also generate boot images for host (for testing), but we don't need those in the apex.
+					// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
+					if variant.target.Os == android.Android {
+						files[variant.target.Arch.ArchType] = variant.imagesDeps
+					}
+				}
+				info.bootImageFilesByArch = files
+			}
+		}
 	}
 
 	// Make the apex content info available for other modules.
@@ -425,12 +447,12 @@
 
 // populateApexContentInfoDexJars adds paths to the dex jars provided by this fragment to the
 // apex content info.
-func (b *BootclasspathFragmentModule) populateApexContentInfoDexJars(ctx android.ModuleContext, info *BootclasspathFragmentApexContentInfo, contents []android.Module, hiddenAPIInfo *hiddenAPIFlagFileInfo) {
+func (b *BootclasspathFragmentModule) populateApexContentInfoDexJars(ctx android.ModuleContext, info *BootclasspathFragmentApexContentInfo, contents []android.Module, hiddenAPIFlagOutput *HiddenAPIFlagOutput) {
 
 	info.contentModuleDexJarPaths = map[string]android.Path{}
-	if hiddenAPIInfo != nil {
+	if hiddenAPIFlagOutput != nil {
 		// Hidden API encoding has been performed.
-		flags := hiddenAPIInfo.AllFlagsPaths[0]
+		flags := hiddenAPIFlagOutput.AllFlagsPath
 		for _, m := range contents {
 			h := m.(hiddenAPIModule)
 			unencodedDex := h.bootDexJar()
@@ -499,109 +521,104 @@
 	return imageConfig
 }
 
-// canPerformHiddenAPIProcessing determines whether hidden API processing should be performed.
-//
-// A temporary workaround to avoid existing bootclasspath_fragments that do not provide the
-// appropriate information needed for hidden API processing breaking the build.
-// TODO(b/179354495): Remove this workaround.
-func (b *BootclasspathFragmentModule) canPerformHiddenAPIProcessing(ctx android.ModuleContext) bool {
-	// Hidden API processing is always enabled in tests.
-	if ctx.Config().TestProductVariables != nil {
-		return true
-	}
-	// A module that has fragments should have access to the information it needs in order to perform
-	// hidden API processing.
-	if len(b.properties.Fragments) != 0 {
-		return true
+// generateHiddenAPIBuildActions generates all the hidden API related build rules.
+func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) *HiddenAPIFlagOutput {
+
+	// Create hidden API input structure.
+	input := b.createHiddenAPIFlagInput(ctx, contents, fragments)
+
+	var output *HiddenAPIFlagOutput
+
+	// Hidden API processing is conditional as a temporary workaround as not all
+	// bootclasspath_fragments provide the appropriate information needed for hidden API processing
+	// which leads to breakages of the build.
+	// TODO(b/179354495): Stop hidden API processing being conditional once all bootclasspath_fragment
+	//  modules have been updated to support it.
+	if input.canPerformHiddenAPIProcessing(ctx, b.properties) {
+		// Get the content modules that contribute to the hidden API processing.
+		hiddenAPIModules := gatherHiddenAPIModuleFromContents(ctx, contents)
+
+		// Delegate the production of the hidden API all-flags.csv file to a module type specific method.
+		common := ctx.Module().(commonBootclasspathFragment)
+		output = common.produceHiddenAPIAllFlagsFile(ctx, hiddenAPIModules, input)
 	}
 
-	// The art bootclasspath fragment does not depend on any other fragments but already supports
-	// hidden API processing.
-	imageName := proptools.String(b.properties.Image_name)
-	if imageName == "art" {
-		return true
+	// Initialize a HiddenAPIInfo structure.
+	hiddenAPIInfo := HiddenAPIInfo{
+		// The monolithic hidden API processing needs access to the flag files that override the default
+		// flags from all the fragments whether or not they actually perform their own hidden API flag
+		// generation. That is because the monolithic hidden API processing uses those flag files to
+		// perform its own flag generation.
+		FlagFilesByCategory: input.FlagFilesByCategory,
+
+		// Other bootclasspath_fragments that depend on this need the transitive set of stub dex jars
+		// from this to resolve any references from their code to classes provided by this fragment
+		// and the fragments this depends upon.
+		TransitiveStubDexJarsByKind: input.transitiveStubDexJarsByKind(),
 	}
 
-	// Disable it for everything else.
-	return false
+	if output != nil {
+		// The monolithic hidden API processing also needs access to all the output files produced by
+		// hidden API processing of this fragment.
+		hiddenAPIInfo.HiddenAPIFlagOutput = *output
+	}
+
+	//  Provide it for use by other modules.
+	ctx.SetProvider(HiddenAPIInfoProvider, hiddenAPIInfo)
+
+	return output
 }
 
-// generateHiddenAPIBuildActions generates all the hidden API related build rules.
-func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, contents []android.Module) *hiddenAPIFlagFileInfo {
+// createHiddenAPIFlagInput creates a HiddenAPIFlagInput struct and initializes it with information derived
+// from the properties on this module and its dependencies.
+func (b *BootclasspathFragmentModule) createHiddenAPIFlagInput(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) HiddenAPIFlagInput {
 
-	// A temporary workaround to avoid existing bootclasspath_fragments that do not provide the
-	// appropriate information needed for hidden API processing breaking the build.
-	if !b.canPerformHiddenAPIProcessing(ctx) {
-		// Nothing to do.
-		return nil
-	}
+	// Merge the HiddenAPIInfo from all the fragment dependencies.
+	dependencyHiddenApiInfo := newHiddenAPIInfo()
+	dependencyHiddenApiInfo.mergeFromFragmentDeps(ctx, fragments)
 
-	// Convert the kind specific lists of modules into kind specific lists of jars.
-	stubJarsByKind := hiddenAPIGatherStubLibDexJarPaths(ctx, contents)
+	// Create hidden API flag input structure.
+	input := newHiddenAPIFlagInput()
 
-	// Performing hidden API processing without stubs is not supported and it is unlikely to ever be
-	// required as the whole point of adding something to the bootclasspath fragment is to add it to
-	// the bootclasspath in order to be used by something else in the system. Without any stubs it
-	// cannot do that.
-	if len(stubJarsByKind) == 0 {
-		return nil
-	}
+	// Update the input structure with information obtained from the stub libraries.
+	input.gatherStubLibInfo(ctx, contents)
 
-	// Store the information for use by other modules.
-	bootclasspathApiInfo := bootclasspathApiInfo{stubJarsByKind: stubJarsByKind}
-	ctx.SetProvider(bootclasspathApiInfoProvider, bootclasspathApiInfo)
+	// Populate with flag file paths from the properties.
+	input.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api)
 
-	// Resolve the properties to paths.
-	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
+	// Store the stub dex jars from this module's fragment dependencies.
+	input.DependencyStubDexJarsByKind = dependencyHiddenApiInfo.TransitiveStubDexJarsByKind
 
-	hiddenAPIModules := gatherHiddenAPIModuleFromContents(ctx, contents)
-
-	// Delegate the production of the hidden API all flags file to a module type specific method.
-	common := ctx.Module().(commonBootclasspathFragment)
-	common.produceHiddenAPIAllFlagsFile(ctx, hiddenAPIModules, stubJarsByKind, &flagFileInfo)
-
-	// Store the information for use by platform_bootclasspath.
-	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
-
-	return &flagFileInfo
+	return input
 }
 
 // produceHiddenAPIAllFlagsFile produces the hidden API all-flags.csv file (and supporting files)
 // for the fragment.
-func (b *BootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, stubJarsByKind map[android.SdkKind]android.Paths, flagFileInfo *hiddenAPIFlagFileInfo) {
-	// Generate the rules to create the hidden API flags and update the supplied flagFileInfo with the
+func (b *BootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, input HiddenAPIFlagInput) *HiddenAPIFlagOutput {
+	// Generate the rules to create the hidden API flags and update the supplied hiddenAPIInfo with the
 	// paths to the created files.
-	hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx, contents, stubJarsByKind, flagFileInfo)
+	return hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx, contents, input)
 }
 
 // generateBootImageBuildActions generates ninja rules to create the boot image if required for this
 // module.
-func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.ModuleContext, contents []android.Module) {
+//
+// Returns true if the boot image is created, false otherwise.
+func (b *BootclasspathFragmentModule) generateBootImageBuildActions(ctx android.ModuleContext, contents []android.Module, imageConfig *bootImageConfig) bool {
 	global := dexpreopt.GetGlobalConfig(ctx)
 	if !shouldBuildBootImages(ctx.Config(), global) {
-		return
-	}
-
-	// Bootclasspath fragment modules that are not preferred do not produce a boot image.
-	if !isActiveModule(ctx.Module()) {
-		return
-	}
-
-	// Bootclasspath fragment modules that have no image_name property do not produce a boot image.
-	imageConfig := b.getImageConfig(ctx)
-	if imageConfig == nil {
-		return
+		return false
 	}
 
 	// Bootclasspath fragment modules that are for the platform do not produce a boot image.
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if apexInfo.IsForPlatform() {
-		return
+		return false
 	}
 
 	// Bootclasspath fragment modules that are versioned do not produce a boot image.
 	if android.IsModuleInVersionedSdk(ctx.Module()) {
-		return
+		return false
 	}
 
 	// Copy the dex jars of this fragment's content modules to their predefined locations.
@@ -610,6 +627,8 @@
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
 	buildBootImage(ctx, imageConfig, profile)
+
+	return true
 }
 
 type bootclasspathFragmentMemberType struct {
@@ -651,7 +670,7 @@
 	Core_platform_stub_libs []string
 
 	// Flag files by *hiddenAPIFlagFileCategory
-	Flag_files_by_category map[*hiddenAPIFlagFileCategory]android.Paths
+	Flag_files_by_category FlagFilesByCategory
 
 	// The path to the generated stub-flags.csv file.
 	Stub_flags_path android.OptionalPath
@@ -669,34 +688,23 @@
 	All_flags_path android.OptionalPath
 }
 
-func pathsToOptionalPath(paths android.Paths) android.OptionalPath {
-	switch len(paths) {
-	case 0:
-		return android.OptionalPath{}
-	case 1:
-		return android.OptionalPathForPath(paths[0])
-	default:
-		panic(fmt.Errorf("expected 0 or 1 paths, found %q", paths))
-	}
-}
-
 func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	module := variant.(*BootclasspathFragmentModule)
 
 	b.Image_name = module.properties.Image_name
 	b.Contents = module.properties.Contents
 
-	// Get the flag file information from the module.
+	// Get the hidden API information from the module.
 	mctx := ctx.SdkModuleContext()
-	flagFileInfo := mctx.OtherModuleProvider(module, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
-	b.Flag_files_by_category = flagFileInfo.categoryToPaths
+	hiddenAPIInfo := mctx.OtherModuleProvider(module, HiddenAPIInfoProvider).(HiddenAPIInfo)
+	b.Flag_files_by_category = hiddenAPIInfo.FlagFilesByCategory
 
 	// Copy all the generated file paths.
-	b.Stub_flags_path = pathsToOptionalPath(flagFileInfo.StubFlagsPaths)
-	b.Annotation_flags_path = pathsToOptionalPath(flagFileInfo.AnnotationFlagsPaths)
-	b.Metadata_path = pathsToOptionalPath(flagFileInfo.MetadataPaths)
-	b.Index_path = pathsToOptionalPath(flagFileInfo.IndexPaths)
-	b.All_flags_path = pathsToOptionalPath(flagFileInfo.AllFlagsPaths)
+	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
+	b.Annotation_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AnnotationFlagsPath)
+	b.Metadata_path = android.OptionalPathForPath(hiddenAPIInfo.MetadataPath)
+	b.Index_path = android.OptionalPathForPath(hiddenAPIInfo.IndexPath)
+	b.All_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AllFlagsPath)
 
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
@@ -806,20 +814,24 @@
 
 // produceHiddenAPIAllFlagsFile returns a path to the prebuilt all-flags.csv or nil if none is
 // specified.
-func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, stubJarsByKind map[android.SdkKind]android.Paths, flagFileInfo *hiddenAPIFlagFileInfo) {
-	pathsForOptionalSrc := func(src *string) android.Paths {
+func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIAllFlagsFile(ctx android.ModuleContext, contents []hiddenAPIModule, _ HiddenAPIFlagInput) *HiddenAPIFlagOutput {
+	pathForOptionalSrc := func(src *string) android.Path {
 		if src == nil {
 			// TODO(b/179354495): Fail if this is not provided once prebuilts have been updated.
 			return nil
 		}
-		return android.Paths{android.PathForModuleSrc(ctx, *src)}
+		return android.PathForModuleSrc(ctx, *src)
 	}
 
-	flagFileInfo.StubFlagsPaths = pathsForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags)
-	flagFileInfo.AnnotationFlagsPaths = pathsForOptionalSrc(module.prebuiltProperties.Hidden_api.Annotation_flags)
-	flagFileInfo.MetadataPaths = pathsForOptionalSrc(module.prebuiltProperties.Hidden_api.Metadata)
-	flagFileInfo.IndexPaths = pathsForOptionalSrc(module.prebuiltProperties.Hidden_api.Index)
-	flagFileInfo.AllFlagsPaths = pathsForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags)
+	output := HiddenAPIFlagOutput{
+		StubFlagsPath:       pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags),
+		AnnotationFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Annotation_flags),
+		MetadataPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Metadata),
+		IndexPath:           pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Index),
+		AllFlagsPath:        pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags),
+	}
+
+	return &output
 }
 
 var _ commonBootclasspathFragment = (*prebuiltBootclasspathFragmentModule)(nil)
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index db284c9..fba7d1a 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -29,38 +29,28 @@
 	dexpreopt.PrepareForTestByEnablingDexpreopt,
 )
 
-func TestUnknownBootclasspathFragment(t *testing.T) {
+func TestBootclasspathFragment_UnknownImageName(t *testing.T) {
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
 		RunTestWithBp(t, `
 			bootclasspath_fragment {
 				name: "unknown-bootclasspath-fragment",
 				image_name: "unknown",
+				contents: ["foo"],
 			}
 		`)
 }
 
-func TestUnknownBootclasspathFragmentImageName(t *testing.T) {
+func TestPrebuiltBootclasspathFragment_UnknownImageName(t *testing.T) {
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
-		RunTestWithBp(t, `
-			bootclasspath_fragment {
-				name: "unknown-bootclasspath-fragment",
-				image_name: "unknown",
-			}
-		`)
-}
-
-func TestUnknownPrebuiltBootclasspathFragment(t *testing.T) {
-	prepareForTestWithBootclasspathFragment.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
 		RunTestWithBp(t, `
 			prebuilt_bootclasspath_fragment {
 				name: "unknown-bootclasspath-fragment",
 				image_name: "unknown",
+				contents: ["foo"],
 			}
 		`)
 }
@@ -76,6 +66,7 @@
 			bootclasspath_fragment {
 				name: "bootclasspath-fragment",
 				image_name: "art",
+				contents: ["foo", "bar"],
 				apex_available: [
 					"apex",
 				],
@@ -94,6 +85,7 @@
 			bootclasspath_fragment {
 				name: "bootclasspath-fragment",
 				image_name: "art",
+				contents: ["foo", "bar"],
 				apex_available: [
 					"apex1",
 					"apex2",
@@ -102,17 +94,6 @@
 		`)
 }
 
-func TestBootclasspathFragmentWithoutImageNameOrContents(t *testing.T) {
-	prepareForTestWithBootclasspathFragment.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`\Qneither of the "image_name" and "contents" properties\E`)).
-		RunTestWithBp(t, `
-			bootclasspath_fragment {
-				name: "bootclasspath-fragment",
-			}
-		`)
-}
-
 func TestBootclasspathFragment_Coverage(t *testing.T) {
 	prepareForTestWithFrameworkCoverage := android.FixtureMergeEnv(map[string]string{
 		"EMMA_INSTRUMENT":           "true",
@@ -252,7 +233,7 @@
 	`)
 
 	fragment := result.Module("myfragment", "android_common")
-	info := result.ModuleProvider(fragment, bootclasspathApiInfoProvider).(bootclasspathApiInfo)
+	info := result.ModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
 
 	stubsJar := "out/soong/.intermediates/mystublib/android_common/dex/mystublib.jar"
 
@@ -264,17 +245,17 @@
 	otherPublicStubsJar := "out/soong/.intermediates/myothersdklibrary.stubs/android_common/dex/myothersdklibrary.stubs.jar"
 
 	// Check that SdkPublic uses public stubs for all sdk libraries.
-	android.AssertPathsRelativeToTopEquals(t, "public dex stubs jar", []string{otherPublicStubsJar, publicStubsJar, stubsJar}, info.stubJarsByKind[android.SdkPublic])
+	android.AssertPathsRelativeToTopEquals(t, "public dex stubs jar", []string{otherPublicStubsJar, publicStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkPublic])
 
 	// Check that SdkSystem uses system stubs for mysdklibrary and public stubs for myothersdklibrary
 	// as it does not provide system stubs.
-	android.AssertPathsRelativeToTopEquals(t, "system dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.stubJarsByKind[android.SdkSystem])
+	android.AssertPathsRelativeToTopEquals(t, "system dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkSystem])
 
 	// Check that SdkTest also uses system stubs for mysdklibrary as it does not provide test stubs
 	// and public stubs for myothersdklibrary as it does not provide test stubs either.
-	android.AssertPathsRelativeToTopEquals(t, "test dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.stubJarsByKind[android.SdkTest])
+	android.AssertPathsRelativeToTopEquals(t, "test dex stubs jar", []string{otherPublicStubsJar, systemStubsJar, stubsJar}, info.TransitiveStubDexJarsByKind[android.SdkTest])
 
 	// Check that SdkCorePlatform uses public stubs from the mycoreplatform library.
 	corePlatformStubsJar := "out/soong/.intermediates/mycoreplatform.stubs/android_common/dex/mycoreplatform.stubs.jar"
-	android.AssertPathsRelativeToTopEquals(t, "core platform dex stubs jar", []string{corePlatformStubsJar}, info.stubJarsByKind[android.SdkCorePlatform])
+	android.AssertPathsRelativeToTopEquals(t, "core platform dex stubs jar", []string{corePlatformStubsJar}, info.TransitiveStubDexJarsByKind[android.SdkCorePlatform])
 }
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 566f7e3..9531056 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -546,7 +546,8 @@
 			`\n` +
 			`If it is not possible to do so, there are workarounds:\n` +
 			`\n` +
-			`1. You can suppress the errors with @SuppressLint("<id>")\n`
+			`1. You can suppress the errors with @SuppressLint("<id>")\n` +
+			`   where the <id> is given in brackets in the error message above.\n`
 
 		if baselineFile.Valid() {
 			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index f5afe5d..f2649d3 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // Contains support for processing hiddenAPI in a modular fashion.
@@ -66,6 +67,12 @@
 
 // hiddenAPIRelevantSdkKinds lists all the android.SdkKind instances that are needed by the hidden
 // API processing.
+//
+// These are in order from narrowest API surface to widest. Widest means the API stubs with the
+// biggest API surface, e.g. test is wider than system is wider than public. Core platform is
+// considered wider than test even though it has no relationship with test because the libraries
+// that provide core platform API don't provide test. While the core platform API is being converted
+// to a system API the system API is still a subset of core platform.
 var hiddenAPIRelevantSdkKinds = []android.SdkKind{
 	android.SdkPublic,
 	android.SdkSystem,
@@ -127,42 +134,6 @@
 	}
 }
 
-// hiddenAPIGatherStubLibDexJarPaths gathers the paths to the dex jars from the dependencies added
-// in hiddenAPIAddStubLibDependencies.
-func hiddenAPIGatherStubLibDexJarPaths(ctx android.ModuleContext, contents []android.Module) map[android.SdkKind]android.Paths {
-	m := map[android.SdkKind]android.Paths{}
-
-	// If the contents includes any java_sdk_library modules then add them to the stubs.
-	for _, module := range contents {
-		if _, ok := module.(SdkLibraryDependency); ok {
-			for _, kind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkTest} {
-				dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, kind)
-				if dexJar != nil {
-					m[kind] = append(m[kind], dexJar)
-				}
-			}
-		}
-	}
-
-	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
-		tag := ctx.OtherModuleDependencyTag(module)
-		if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok {
-			kind := hiddenAPIStubsTag.sdkKind
-			dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, kind)
-			if dexJar != nil {
-				m[kind] = append(m[kind], dexJar)
-			}
-		}
-	})
-
-	// Normalize the paths, i.e. remove duplicates and sort.
-	for k, v := range m {
-		m[k] = android.SortedUniquePaths(v)
-	}
-
-	return m
-}
-
 // hiddenAPIRetrieveDexJarBuildPath retrieves the DexJarBuildPath from the specified module, if
 // available, or reports an error.
 func hiddenAPIRetrieveDexJarBuildPath(ctx android.ModuleContext, module android.Module, kind android.SdkKind) android.Path {
@@ -193,20 +164,36 @@
 //
 // The rule is initialized but not built so that the caller can modify it and select an appropriate
 // name.
-func ruleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, outputPath android.WritablePath, bootDexJars android.Paths, sdkKindToPathList map[android.SdkKind]android.Paths) *android.RuleBuilder {
+func ruleToGenerateHiddenAPIStubFlagsFile(ctx android.BuilderContext, outputPath android.WritablePath, bootDexJars android.Paths, input HiddenAPIFlagInput) *android.RuleBuilder {
 	// Singleton rule which applies hiddenapi on all boot class path dex files.
 	rule := android.NewRuleBuilder(pctx, ctx)
 
 	tempPath := tempPathForRestat(ctx, outputPath)
 
+	// Find the widest API stubs provided by the fragments on which this depends, if any.
+	var dependencyStubDexJars android.Paths
+	for i := len(hiddenAPIRelevantSdkKinds) - 1; i >= 0; i-- {
+		kind := hiddenAPIRelevantSdkKinds[i]
+		stubsForKind := input.DependencyStubDexJarsByKind[kind]
+		if len(stubsForKind) != 0 {
+			dependencyStubDexJars = stubsForKind
+			break
+		}
+	}
+
 	command := rule.Command().
 		Tool(ctx.Config().HostToolPath(ctx, "hiddenapi")).
 		Text("list").
+		FlagForEachInput("--dependency-stub-dex=", dependencyStubDexJars).
 		FlagForEachInput("--boot-dex=", bootDexJars)
 
 	// Iterate over the sdk kinds in a fixed order.
 	for _, sdkKind := range hiddenAPIRelevantSdkKinds {
-		paths := sdkKindToPathList[sdkKind]
+		// Merge in the stub dex jar paths for this kind from the fragments on which it depends. They
+		// will be needed to resolve dependencies from this fragment's stubs to classes in the other
+		// fragment's APIs.
+		dependencyPaths := input.DependencyStubDexJarsByKind[sdkKind]
+		paths := append(dependencyPaths, input.StubDexJarsByKind[sdkKind]...)
 		if len(paths) > 0 {
 			option := sdkKindToHiddenapiListOption[sdkKind]
 			command.FlagWithInputList("--"+option+"=", paths, ":")
@@ -260,15 +247,6 @@
 	Unsupported_packages []string `android:"path"`
 }
 
-func (p *HiddenAPIFlagFileProperties) hiddenAPIFlagFileInfo(ctx android.ModuleContext) hiddenAPIFlagFileInfo {
-	info := hiddenAPIFlagFileInfo{categoryToPaths: map[*hiddenAPIFlagFileCategory]android.Paths{}}
-	for _, category := range hiddenAPIFlagFileCategories {
-		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
-		info.categoryToPaths[category] = paths
-	}
-	return info
-}
-
 type hiddenAPIFlagFileCategory struct {
 	// propertyName is the name of the property for this category.
 	propertyName string
@@ -282,6 +260,22 @@
 	commandMutator func(command *android.RuleBuilderCommand, path android.Path)
 }
 
+// The flag file category for removed members of the API.
+//
+// This is extracted from hiddenAPIFlagFileCategories as it is needed to add the dex signatures
+// list of removed API members that are generated automatically from the removed.txt files provided
+// by API stubs.
+var hiddenAPIRemovedFlagFileCategory = &hiddenAPIFlagFileCategory{
+	// See HiddenAPIFlagFileProperties.Removed
+	propertyName: "removed",
+	propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
+		return properties.Removed
+	},
+	commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
+		command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
+	},
+}
+
 var hiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
 	// See HiddenAPIFlagFileProperties.Unsupported
 	{
@@ -293,16 +287,7 @@
 			command.FlagWithInput("--unsupported ", path)
 		},
 	},
-	// See HiddenAPIFlagFileProperties.Removed
-	{
-		propertyName: "removed",
-		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
-			return properties.Removed
-		},
-		commandMutator: func(command *android.RuleBuilderCommand, path android.Path) {
-			command.FlagWithInput("--unsupported ", path).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed")
-		},
-	},
+	hiddenAPIRemovedFlagFileCategory,
 	// See HiddenAPIFlagFileProperties.Max_target_r_low_priority
 	{
 		propertyName: "max_target_r_low_priority",
@@ -365,45 +350,226 @@
 	},
 }
 
-// hiddenAPIFlagFileInfo contains paths resolved from HiddenAPIFlagFileProperties and also generated
-// by hidden API processing.
-//
-// This is used both for an individual bootclasspath_fragment to provide it to other modules and
-// for a module to collate the files from the fragments it depends upon. That is why the fields are
-// all Paths even though they are initialized with a single path.
-type hiddenAPIFlagFileInfo struct {
-	// categoryToPaths maps from the flag file category to the paths containing information for that
-	// category.
-	categoryToPaths map[*hiddenAPIFlagFileCategory]android.Paths
+// FlagFilesByCategory maps a hiddenAPIFlagFileCategory to the paths to the files in that category.
+type FlagFilesByCategory map[*hiddenAPIFlagFileCategory]android.Paths
 
-	// The paths to the generated stub-flags.csv files.
-	StubFlagsPaths android.Paths
-
-	// The paths to the generated annotation-flags.csv files.
-	AnnotationFlagsPaths android.Paths
-
-	// The paths to the generated metadata.csv files.
-	MetadataPaths android.Paths
-
-	// The paths to the generated index.csv files.
-	IndexPaths android.Paths
-
-	// The paths to the generated all-flags.csv files.
-	AllFlagsPaths android.Paths
-}
-
-func (i *hiddenAPIFlagFileInfo) append(other hiddenAPIFlagFileInfo) {
+// append appends the supplied flags files to the corresponding category in this map.
+func (s FlagFilesByCategory) append(other FlagFilesByCategory) {
 	for _, category := range hiddenAPIFlagFileCategories {
-		i.categoryToPaths[category] = append(i.categoryToPaths[category], other.categoryToPaths[category]...)
+		s[category] = append(s[category], other[category]...)
 	}
-	i.StubFlagsPaths = append(i.StubFlagsPaths, other.StubFlagsPaths...)
-	i.AnnotationFlagsPaths = append(i.AnnotationFlagsPaths, other.AnnotationFlagsPaths...)
-	i.MetadataPaths = append(i.MetadataPaths, other.MetadataPaths...)
-	i.IndexPaths = append(i.IndexPaths, other.IndexPaths...)
-	i.AllFlagsPaths = append(i.AllFlagsPaths, other.AllFlagsPaths...)
 }
 
-var hiddenAPIFlagFileInfoProvider = blueprint.NewProvider(hiddenAPIFlagFileInfo{})
+// dedup removes duplicates in the flag files, while maintaining the order in which they were
+// appended.
+func (s FlagFilesByCategory) dedup() {
+	for category, paths := range s {
+		s[category] = android.FirstUniquePaths(paths)
+	}
+}
+
+// HiddenAPIInfo contains information provided by the hidden API processing.
+//
+// That includes paths resolved from HiddenAPIFlagFileProperties and also generated by hidden API
+// processing.
+type HiddenAPIInfo struct {
+	// FlagFilesByCategory maps from the flag file category to the paths containing information for
+	// that category.
+	FlagFilesByCategory FlagFilesByCategory
+
+	// The paths to the stub dex jars for each of the android.SdkKind in hiddenAPIRelevantSdkKinds.
+	TransitiveStubDexJarsByKind StubDexJarsByKind
+
+	// The output from the hidden API processing needs to be made available to other modules.
+	HiddenAPIFlagOutput
+}
+
+func newHiddenAPIInfo() *HiddenAPIInfo {
+	info := HiddenAPIInfo{
+		FlagFilesByCategory:         FlagFilesByCategory{},
+		TransitiveStubDexJarsByKind: StubDexJarsByKind{},
+	}
+	return &info
+}
+
+func (i *HiddenAPIInfo) mergeFromFragmentDeps(ctx android.ModuleContext, fragments []android.Module) {
+	// Merge all the information from the fragments. The fragments form a DAG so it is possible that
+	// this will introduce duplicates so they will be resolved after processing all the fragments.
+	for _, fragment := range fragments {
+		if ctx.OtherModuleHasProvider(fragment, HiddenAPIInfoProvider) {
+			info := ctx.OtherModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
+			i.TransitiveStubDexJarsByKind.append(info.TransitiveStubDexJarsByKind)
+		}
+	}
+
+	// Dedup and sort paths.
+	i.TransitiveStubDexJarsByKind.dedupAndSort()
+}
+
+var HiddenAPIInfoProvider = blueprint.NewProvider(HiddenAPIInfo{})
+
+// StubDexJarsByKind maps an android.SdkKind to the paths to stub dex jars appropriate for that
+// level. See hiddenAPIRelevantSdkKinds for a list of the acceptable android.SdkKind values.
+type StubDexJarsByKind map[android.SdkKind]android.Paths
+
+// append appends the supplied kind specific stub dex jar pargs to the corresponding kind in this
+// map.
+func (s StubDexJarsByKind) append(other StubDexJarsByKind) {
+	for _, kind := range hiddenAPIRelevantSdkKinds {
+		s[kind] = append(s[kind], other[kind]...)
+	}
+}
+
+// dedupAndSort removes duplicates in the stub dex jar paths and sorts them into a consistent and
+// deterministic order.
+func (s StubDexJarsByKind) dedupAndSort() {
+	for kind, paths := range s {
+		s[kind] = android.SortedUniquePaths(paths)
+	}
+}
+
+// HiddenAPIFlagInput encapsulates information obtained from a module and its dependencies that are
+// needed for hidden API flag generation.
+type HiddenAPIFlagInput struct {
+	// FlagFilesByCategory contains the flag files that override the initial flags that are derived
+	// from the stub dex files.
+	FlagFilesByCategory FlagFilesByCategory
+
+	// StubDexJarsByKind contains the stub dex jars for different android.SdkKind and which determine
+	// the initial flags for each dex member.
+	StubDexJarsByKind StubDexJarsByKind
+
+	// DependencyStubDexJarsByKind contains the stub dex jars provided by the fragments on which this
+	// depends. It is the result of merging HiddenAPIInfo.TransitiveStubDexJarsByKind from each
+	// fragment on which this depends.
+	DependencyStubDexJarsByKind StubDexJarsByKind
+
+	// RemovedTxtFiles is the list of removed.txt files provided by java_sdk_library modules that are
+	// specified in the bootclasspath_fragment's stub_libs and contents properties.
+	RemovedTxtFiles android.Paths
+}
+
+// newHiddenAPIFlagInput creates a new initialize HiddenAPIFlagInput struct.
+func newHiddenAPIFlagInput() HiddenAPIFlagInput {
+	input := HiddenAPIFlagInput{
+		FlagFilesByCategory: FlagFilesByCategory{},
+		StubDexJarsByKind:   StubDexJarsByKind{},
+	}
+
+	return input
+}
+
+// canPerformHiddenAPIProcessing determines whether hidden API processing should be performed.
+//
+// A temporary workaround to avoid existing bootclasspath_fragments that do not provide the
+// appropriate information needed for hidden API processing breaking the build.
+// TODO(b/179354495): Remove this workaround.
+func (i *HiddenAPIFlagInput) canPerformHiddenAPIProcessing(ctx android.ModuleContext, properties bootclasspathFragmentProperties) bool {
+	// Performing hidden API processing without stubs is not supported and it is unlikely to ever be
+	// required as the whole point of adding something to the bootclasspath fragment is to add it to
+	// the bootclasspath in order to be used by something else in the system. Without any stubs it
+	// cannot do that.
+	if len(i.StubDexJarsByKind) == 0 {
+		return false
+	}
+
+	// Hidden API processing is always enabled in tests.
+	if ctx.Config().TestProductVariables != nil {
+		return true
+	}
+
+	// A module that has fragments should have access to the information it needs in order to perform
+	// hidden API processing.
+	if len(properties.Fragments) != 0 {
+		return true
+	}
+
+	// The art bootclasspath fragment does not depend on any other fragments but already supports
+	// hidden API processing.
+	imageName := proptools.String(properties.Image_name)
+	if imageName == "art" {
+		return true
+	}
+
+	// Disable it for everything else.
+	return false
+}
+
+// gatherStubLibInfo gathers information from the stub libs needed by hidden API processing from the
+// dependencies added in hiddenAPIAddStubLibDependencies.
+//
+// That includes paths to the stub dex jars as well as paths to the *removed.txt files.
+func (i *HiddenAPIFlagInput) gatherStubLibInfo(ctx android.ModuleContext, contents []android.Module) {
+	addFromModule := func(ctx android.ModuleContext, module android.Module, kind android.SdkKind) {
+		dexJar := hiddenAPIRetrieveDexJarBuildPath(ctx, module, kind)
+		if dexJar != nil {
+			i.StubDexJarsByKind[kind] = append(i.StubDexJarsByKind[kind], dexJar)
+		}
+
+		if sdkLibrary, ok := module.(SdkLibraryDependency); ok {
+			removedTxtFile := sdkLibrary.SdkRemovedTxtFile(ctx, kind)
+			i.RemovedTxtFiles = append(i.RemovedTxtFiles, removedTxtFile.AsPaths()...)
+		}
+	}
+
+	// If the contents includes any java_sdk_library modules then add them to the stubs.
+	for _, module := range contents {
+		if _, ok := module.(SdkLibraryDependency); ok {
+			// Add information for every possible kind needed by hidden API. SdkCorePlatform is not used
+			// as the java_sdk_library does not have special support for core_platform API, instead it is
+			// implemented as a customized form of SdkPublic.
+			for _, kind := range []android.SdkKind{android.SdkPublic, android.SdkSystem, android.SdkTest} {
+				addFromModule(ctx, module, kind)
+			}
+		}
+	}
+
+	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
+		tag := ctx.OtherModuleDependencyTag(module)
+		if hiddenAPIStubsTag, ok := tag.(hiddenAPIStubsDependencyTag); ok {
+			kind := hiddenAPIStubsTag.sdkKind
+			addFromModule(ctx, module, kind)
+		}
+	})
+
+	// Normalize the paths, i.e. remove duplicates and sort.
+	i.StubDexJarsByKind.dedupAndSort()
+	i.RemovedTxtFiles = android.SortedUniquePaths(i.RemovedTxtFiles)
+}
+
+// extractFlagFilesFromProperties extracts the paths to flag files that are specified in the
+// supplied properties and stores them in this struct.
+func (i *HiddenAPIFlagInput) extractFlagFilesFromProperties(ctx android.ModuleContext, p *HiddenAPIFlagFileProperties) {
+	for _, category := range hiddenAPIFlagFileCategories {
+		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
+		i.FlagFilesByCategory[category] = paths
+	}
+}
+
+func (i *HiddenAPIFlagInput) transitiveStubDexJarsByKind() StubDexJarsByKind {
+	transitive := i.DependencyStubDexJarsByKind
+	transitive.append(i.StubDexJarsByKind)
+	return transitive
+}
+
+// HiddenAPIFlagOutput contains paths to output files from the hidden API flag generation for a
+// bootclasspath_fragment module.
+type HiddenAPIFlagOutput struct {
+	// The path to the generated stub-flags.csv file.
+	StubFlagsPath android.Path
+
+	// The path to the generated annotation-flags.csv file.
+	AnnotationFlagsPath android.Path
+
+	// The path to the generated metadata.csv file.
+	MetadataPath android.Path
+
+	// The path to the generated index.csv file.
+	IndexPath android.Path
+
+	// The path to the generated all-flags.csv file.
+	AllFlagsPath android.Path
+}
 
 // pathForValidation creates a path of the same type as the supplied type but with a name of
 // <path>.valid.
@@ -426,16 +592,18 @@
 // annotationFlags is the path to the annotation flags file generated from annotation information
 // in each module.
 //
-// flagFileInfo is a struct containing paths to files that augment the information provided by
+// hiddenAPIInfo is a struct containing paths to files that augment the information provided by
 // the annotationFlags.
-func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc string, outputPath android.WritablePath, baseFlagsPath android.Path, annotationFlags android.Path, flagFileInfo *hiddenAPIFlagFileInfo) {
+func buildRuleToGenerateHiddenApiFlags(ctx android.BuilderContext, name, desc string,
+	outputPath android.WritablePath, baseFlagsPath android.Path, annotationFlags android.Path,
+	flagFilesByCategory FlagFilesByCategory, allFlagsPaths android.Paths, generatedRemovedDexSignatures android.OptionalPath) {
 
 	// The file which is used to record that the flags file is valid.
 	var validFile android.WritablePath
 
 	// If there are flag files that have been generated by fragments on which this depends then use
 	// them to validate the flag file generated by the rules created by this method.
-	if allFlagsPaths := flagFileInfo.AllFlagsPaths; len(allFlagsPaths) > 0 {
+	if len(allFlagsPaths) > 0 {
 		// The flags file generated by the rule created by this method needs to be validated to ensure
 		// that it is consistent with the flag files generated by the individual fragments.
 
@@ -463,12 +631,18 @@
 
 	// Add the options for the different categories of flag files.
 	for _, category := range hiddenAPIFlagFileCategories {
-		paths := flagFileInfo.categoryToPaths[category]
+		paths := flagFilesByCategory[category]
 		for _, path := range paths {
 			category.commandMutator(command, path)
 		}
 	}
 
+	// If available then pass the automatically generated file containing dex signatures of removed
+	// API members to the rule so they can be marked as removed.
+	if generatedRemovedDexSignatures.Valid() {
+		hiddenAPIRemovedFlagFileCategory.commandMutator(command, generatedRemovedDexSignatures.Path())
+	}
+
 	commitChangeForRestat(rule, tempPath, outputPath)
 
 	if validFile != nil {
@@ -496,13 +670,15 @@
 // * metadata.csv
 // * index.csv
 // * all-flags.csv
-func hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx android.ModuleContext, contents []hiddenAPIModule, stubJarsByKind map[android.SdkKind]android.Paths, flagFileInfo *hiddenAPIFlagFileInfo) {
+func hiddenAPIGenerateAllFlagsForBootclasspathFragment(ctx android.ModuleContext, contents []hiddenAPIModule, input HiddenAPIFlagInput) *HiddenAPIFlagOutput {
 	hiddenApiSubDir := "modular-hiddenapi"
 
-	// Generate the stub-flags.csv.
+	// Gather the dex files for the boot libraries provided by this fragment.
 	bootDexJars := extractBootDexJarsFromHiddenAPIModules(ctx, contents)
+
+	// Generate the stub-flags.csv.
 	stubFlagsCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "stub-flags.csv")
-	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlagsCSV, bootDexJars, stubJarsByKind)
+	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlagsCSV, bootDexJars, input)
 	rule.Build("modularHiddenAPIStubFlagsFile", "modular hiddenapi stub flags")
 
 	// Extract the classes jars from the contents.
@@ -520,24 +696,45 @@
 	indexCSV := android.PathForModuleOut(ctx, hiddenApiSubDir, "index.csv")
 	buildRuleToGenerateIndex(ctx, "modular hiddenapi index", classesJars, indexCSV)
 
-	// Removed APIs need to be marked and in order to do that the flagFileInfo needs to specify files
+	// Removed APIs need to be marked and in order to do that the hiddenAPIInfo needs to specify files
 	// containing dex signatures of all the removed APIs. In the monolithic files that is done by
 	// manually combining all the removed.txt files for each API and then converting them to dex
-	// signatures, see the combined-removed-dex module. That will all be done automatically in future.
-	// For now removed APIs are ignored.
-	// TODO(b/179354495): handle removed apis automatically.
+	// signatures, see the combined-removed-dex module. This does that automatically by using the
+	// *removed.txt files retrieved from the java_sdk_library modules that are specified in the
+	// stub_libs and contents properties of a bootclasspath_fragment.
+	removedDexSignatures := buildRuleToGenerateRemovedDexSignatures(ctx, input.RemovedTxtFiles)
 
 	// Generate the all-flags.csv which are the flags that will, in future, be encoded into the dex
 	// files.
 	outputPath := android.PathForModuleOut(ctx, hiddenApiSubDir, "all-flags.csv")
-	buildRuleToGenerateHiddenApiFlags(ctx, "modularHiddenApiAllFlags", "modular hiddenapi all flags", outputPath, stubFlagsCSV, annotationFlagsCSV, flagFileInfo)
+	buildRuleToGenerateHiddenApiFlags(ctx, "modularHiddenApiAllFlags", "modular hiddenapi all flags", outputPath, stubFlagsCSV, annotationFlagsCSV, input.FlagFilesByCategory, nil, removedDexSignatures)
 
 	// Store the paths in the info for use by other modules and sdk snapshot generation.
-	flagFileInfo.StubFlagsPaths = android.Paths{stubFlagsCSV}
-	flagFileInfo.AnnotationFlagsPaths = android.Paths{annotationFlagsCSV}
-	flagFileInfo.MetadataPaths = android.Paths{metadataCSV}
-	flagFileInfo.IndexPaths = android.Paths{indexCSV}
-	flagFileInfo.AllFlagsPaths = android.Paths{outputPath}
+	output := HiddenAPIFlagOutput{
+		StubFlagsPath:       stubFlagsCSV,
+		AnnotationFlagsPath: annotationFlagsCSV,
+		MetadataPath:        metadataCSV,
+		IndexPath:           indexCSV,
+		AllFlagsPath:        outputPath,
+	}
+	return &output
+}
+
+func buildRuleToGenerateRemovedDexSignatures(ctx android.ModuleContext, removedTxtFiles android.Paths) android.OptionalPath {
+	if len(removedTxtFiles) == 0 {
+		return android.OptionalPath{}
+	}
+
+	output := android.PathForModuleOut(ctx, "modular-hiddenapi/removed-dex-signatures.txt")
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("metalava").
+		Flag("--no-banner").
+		Inputs(removedTxtFiles).
+		FlagWithOutput("--dex-api ", output)
+	rule.Build("modular-hiddenapi-removed-dex-signatures", "modular hiddenapi removed dex signatures")
+	return android.OptionalPathForPath(output)
 }
 
 // gatherHiddenAPIModuleFromContents gathers the hiddenAPIModule from the supplied contents.
diff --git a/java/hiddenapi_monolithic.go b/java/hiddenapi_monolithic.go
new file mode 100644
index 0000000..a6bf8c7
--- /dev/null
+++ b/java/hiddenapi_monolithic.go
@@ -0,0 +1,102 @@
+// Copyright (C) 2021 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.
+
+package java
+
+import (
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+// MonolithicHiddenAPIInfo contains information needed/provided by the hidden API generation of the
+// monolithic hidden API files.
+//
+// Each list of paths includes all the equivalent paths from each of the bootclasspath_fragment
+// modules that contribute to the platform-bootclasspath.
+type MonolithicHiddenAPIInfo struct {
+	// FlagsFilesByCategory maps from the flag file category to the paths containing information for
+	// that category.
+	FlagsFilesByCategory FlagFilesByCategory
+
+	// The paths to the generated stub-flags.csv files.
+	StubFlagsPaths android.Paths
+
+	// The paths to the generated annotation-flags.csv files.
+	AnnotationFlagsPaths android.Paths
+
+	// The paths to the generated metadata.csv files.
+	MetadataPaths android.Paths
+
+	// The paths to the generated index.csv files.
+	IndexPaths android.Paths
+
+	// The paths to the generated all-flags.csv files.
+	AllFlagsPaths android.Paths
+}
+
+// newMonolithicHiddenAPIInfo creates a new MonolithicHiddenAPIInfo from the flagFilesByCategory
+// plus information provided by each of the fragments.
+func newMonolithicHiddenAPIInfo(ctx android.ModuleContext, flagFilesByCategory FlagFilesByCategory, fragments []android.Module) MonolithicHiddenAPIInfo {
+	monolithicInfo := MonolithicHiddenAPIInfo{}
+
+	monolithicInfo.FlagsFilesByCategory = flagFilesByCategory
+
+	// Merge all the information from the fragments. The fragments form a DAG so it is possible that
+	// this will introduce duplicates so they will be resolved after processing all the fragments.
+	for _, fragment := range fragments {
+		if ctx.OtherModuleHasProvider(fragment, HiddenAPIInfoProvider) {
+			info := ctx.OtherModuleProvider(fragment, HiddenAPIInfoProvider).(HiddenAPIInfo)
+			monolithicInfo.append(&info)
+		}
+	}
+
+	// Dedup paths.
+	monolithicInfo.dedup()
+
+	return monolithicInfo
+}
+
+// append appends all the files from the supplied info to the corresponding files in this struct.
+func (i *MonolithicHiddenAPIInfo) append(other *HiddenAPIInfo) {
+	i.FlagsFilesByCategory.append(other.FlagFilesByCategory)
+
+	// The output may not be set if the bootclasspath_fragment has not yet been updated to support
+	// hidden API processing.
+	// TODO(b/179354495): Switch back to append once all bootclasspath_fragment modules have been
+	//  updated to support hidden API processing properly.
+	appendIfNotNil := func(paths android.Paths, path android.Path) android.Paths {
+		if path == nil {
+			return paths
+		}
+		return append(paths, path)
+	}
+	i.StubFlagsPaths = appendIfNotNil(i.StubFlagsPaths, other.StubFlagsPath)
+	i.AnnotationFlagsPaths = appendIfNotNil(i.AnnotationFlagsPaths, other.AnnotationFlagsPath)
+	i.MetadataPaths = appendIfNotNil(i.MetadataPaths, other.MetadataPath)
+	i.IndexPaths = appendIfNotNil(i.IndexPaths, other.IndexPath)
+	i.AllFlagsPaths = appendIfNotNil(i.AllFlagsPaths, other.AllFlagsPath)
+}
+
+// dedup removes duplicates in all the paths, while maintaining the order in which they were
+// appended.
+func (i *MonolithicHiddenAPIInfo) dedup() {
+	i.FlagsFilesByCategory.dedup()
+	i.StubFlagsPaths = android.FirstUniquePaths(i.StubFlagsPaths)
+	i.AnnotationFlagsPaths = android.FirstUniquePaths(i.AnnotationFlagsPaths)
+	i.MetadataPaths = android.FirstUniquePaths(i.MetadataPaths)
+	i.IndexPaths = android.FirstUniquePaths(i.IndexPaths)
+	i.AllFlagsPaths = android.FirstUniquePaths(i.AllFlagsPaths)
+}
+
+var monolithicHiddenAPIInfoProvider = blueprint.NewProvider(MonolithicHiddenAPIInfo{})
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 5db2efe..87c695c 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -279,25 +279,24 @@
 		return
 	}
 
-	flagFileInfo := b.properties.Hidden_api.hiddenAPIFlagFileInfo(ctx)
-	for _, fragment := range fragments {
-		if ctx.OtherModuleHasProvider(fragment, hiddenAPIFlagFileInfoProvider) {
-			info := ctx.OtherModuleProvider(fragment, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
-			flagFileInfo.append(info)
-		}
-	}
+	monolithicInfo := b.createAndProvideMonolithicHiddenAPIInfo(ctx, fragments)
 
-	// Store the information for testing.
-	ctx.SetProvider(hiddenAPIFlagFileInfoProvider, flagFileInfo)
+	// Create the input to pass to ruleToGenerateHiddenAPIStubFlagsFile
+	input := newHiddenAPIFlagInput()
+
+	// Gather stub library information from the dependencies on modules provided by
+	// hiddenAPIComputeMonolithicStubLibModules.
+	input.gatherStubLibInfo(ctx, nil)
+
+	// Use the flag files from this module and all the fragments.
+	input.FlagFilesByCategory = monolithicInfo.FlagsFilesByCategory
 
 	hiddenAPIModules := gatherHiddenAPIModuleFromContents(ctx, modules)
 
-	sdkKindToStubPaths := hiddenAPIGatherStubLibDexJarPaths(ctx, nil)
-
 	// Generate the monolithic stub-flags.csv file.
 	bootDexJars := extractBootDexJarsFromHiddenAPIModules(ctx, hiddenAPIModules)
 	stubFlags := hiddenAPISingletonPaths(ctx).stubFlags
-	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlags, bootDexJars, sdkKindToStubPaths)
+	rule := ruleToGenerateHiddenAPIStubFlagsFile(ctx, stubFlags, bootDexJars, input)
 	rule.Build("platform-bootclasspath-monolithic-hiddenapi-stub-flags", "monolithic hidden API stub flags")
 
 	// Extract the classes jars from the contents.
@@ -309,7 +308,7 @@
 
 	// Generate the monotlithic hiddenapi-flags.csv file.
 	allFlags := hiddenAPISingletonPaths(ctx).flags
-	buildRuleToGenerateHiddenApiFlags(ctx, "hiddenAPIFlagsFile", "hiddenapi flags", allFlags, stubFlags, annotationFlags, &flagFileInfo)
+	buildRuleToGenerateHiddenApiFlags(ctx, "hiddenAPIFlagsFile", "hiddenapi flags", allFlags, stubFlags, annotationFlags, monolithicInfo.FlagsFilesByCategory, monolithicInfo.AllFlagsPaths, android.OptionalPath{})
 
 	// Generate an intermediate monolithic hiddenapi-metadata.csv file directly from the annotations
 	// in the source code.
@@ -328,6 +327,25 @@
 	buildRuleToGenerateIndex(ctx, "monolithic hidden API index", classesJars, indexCSV)
 }
 
+// createAndProvideMonolithicHiddenAPIInfo creates a MonolithicHiddenAPIInfo and provides it for
+// testing.
+func (b *platformBootclasspathModule) createAndProvideMonolithicHiddenAPIInfo(ctx android.ModuleContext, fragments []android.Module) MonolithicHiddenAPIInfo {
+	// Create a temporary input structure in which to collate information provided directly by this
+	// module, either through properties or direct dependencies.
+	temporaryInput := newHiddenAPIFlagInput()
+
+	// Create paths to the flag files specified in the properties.
+	temporaryInput.extractFlagFilesFromProperties(ctx, &b.properties.Hidden_api)
+
+	// Create the monolithic info, by starting with the flag files specified on this and then merging
+	// in information from all the fragment dependencies of this.
+	monolithicInfo := newMonolithicHiddenAPIInfo(ctx, temporaryInput.FlagFilesByCategory, fragments)
+
+	// Store the information for testing.
+	ctx.SetProvider(monolithicHiddenAPIInfoProvider, monolithicInfo)
+	return monolithicInfo
+}
+
 func (b *platformBootclasspathModule) buildRuleMergeCSV(ctx android.ModuleContext, desc string, inputPaths android.Paths, outputPath android.WritablePath) {
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index efcbc80..d332f63 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -245,14 +245,14 @@
 	).RunTest(t)
 
 	pbcp := result.Module("platform-bootclasspath", "android_common")
-	info := result.ModuleProvider(pbcp, hiddenAPIFlagFileInfoProvider).(hiddenAPIFlagFileInfo)
+	info := result.ModuleProvider(pbcp, monolithicHiddenAPIInfoProvider).(MonolithicHiddenAPIInfo)
 
 	for _, category := range hiddenAPIFlagFileCategories {
 		name := category.propertyName
 		message := fmt.Sprintf("category %s", name)
 		filename := strings.ReplaceAll(name, "_", "-")
 		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
-		android.AssertPathsRelativeToTopEquals(t, message, expected, info.categoryToPaths[category])
+		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
 	}
 
 	android.AssertPathsRelativeToTopEquals(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/stub-flags.csv"}, info.StubFlagsPaths)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 6e860e7..8f36758 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -845,19 +845,7 @@
 // closest kind which is a subset of the requested kind. e.g. if requesting android.SdkModule then
 // it will return *scopePaths for android.SdkSystem if available or android.SdkPublic of not.
 func (c *commonToSdkLibraryAndImport) selectScopePaths(ctx android.BaseModuleContext, kind android.SdkKind) *scopePaths {
-	var apiScope *apiScope
-	switch kind {
-	case android.SdkSystem:
-		apiScope = apiScopeSystem
-	case android.SdkModule:
-		apiScope = apiScopeModuleLib
-	case android.SdkTest:
-		apiScope = apiScopeTest
-	case android.SdkSystemServer:
-		apiScope = apiScopeSystemServer
-	default:
-		apiScope = apiScopePublic
-	}
+	apiScope := sdkKindToApiScope(kind)
 
 	paths := c.findClosestScopePath(apiScope)
 	if paths == nil {
@@ -874,6 +862,24 @@
 	return paths
 }
 
+// sdkKindToApiScope maps from android.SdkKind to apiScope.
+func sdkKindToApiScope(kind android.SdkKind) *apiScope {
+	var apiScope *apiScope
+	switch kind {
+	case android.SdkSystem:
+		apiScope = apiScopeSystem
+	case android.SdkModule:
+		apiScope = apiScopeModuleLib
+	case android.SdkTest:
+		apiScope = apiScopeTest
+	case android.SdkSystemServer:
+		apiScope = apiScopeSystemServer
+	default:
+		apiScope = apiScopePublic
+	}
+	return apiScope
+}
+
 // to satisfy SdkLibraryDependency interface
 func (c *commonToSdkLibraryAndImport) SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path {
 	paths := c.selectScopePaths(ctx, kind)
@@ -884,6 +890,17 @@
 	return paths.stubsDexJarPath
 }
 
+// to satisfy SdkLibraryDependency interface
+func (c *commonToSdkLibraryAndImport) SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath {
+	apiScope := sdkKindToApiScope(kind)
+	paths := c.findScopePaths(apiScope)
+	if paths == nil {
+		return android.OptionalPath{}
+	}
+
+	return paths.removedApiFilePath
+}
+
 func (c *commonToSdkLibraryAndImport) sdkComponentPropertiesForChildLibrary() interface{} {
 	componentProps := &struct {
 		SdkLibraryToImplicitlyTrack *string
@@ -964,7 +981,7 @@
 var _ SdkLibraryComponentDependency = (*SdkLibrary)(nil)
 var _ SdkLibraryComponentDependency = (*SdkLibraryImport)(nil)
 
-// Provides access to sdk_version related header and implentation jars.
+// Provides access to sdk_version related files, e.g. header and implementation jars.
 type SdkLibraryDependency interface {
 	SdkLibraryComponentDependency
 
@@ -985,6 +1002,9 @@
 	// tool which processes dex files.
 	SdkApiStubDexJar(ctx android.BaseModuleContext, kind android.SdkKind) android.Path
 
+	// SdkRemovedTxtFile returns the optional path to the removed.txt file for the specified sdk kind.
+	SdkRemovedTxtFile(ctx android.BaseModuleContext, kind android.SdkKind) android.OptionalPath
+
 	// sharedLibrary returns true if this can be used as a shared library.
 	sharedLibrary() bool
 }
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index bd69f06..d9fe281 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -424,6 +424,7 @@
 	android.GroupFixturePreparers(
 		prepareForSdkTestWithApex,
 		prepareForSdkTestWithJava,
+		android.FixtureAddFile("java/mybootlib.jar", nil),
 		android.FixtureWithRootAndroidBp(`
 		sdk {
 			name: "mysdk",
@@ -433,16 +434,27 @@
 		bootclasspath_fragment {
 			name: "mybootclasspathfragment",
 			image_name: "art",
+			contents: ["mybootlib"],
 			apex_available: ["myapex"],
 		}
 
+		java_library {
+			name: "mybootlib",
+			apex_available: ["myapex"],
+			srcs: ["Test.java"],
+			system_modules: "none",
+			sdk_version: "none",
+			min_sdk_version: "1",
+			compile_dex: true,
+		}
+
 		sdk_snapshot {
 			name: "mysdk@1",
-			bootclasspath_fragments: ["mybootclasspathfragment_mysdk_1"],
+			bootclasspath_fragments: ["mysdk_mybootclasspathfragment@1"],
 		}
 
 		prebuilt_bootclasspath_fragment {
-			name: "mybootclasspathfragment_mysdk_1",
+			name: "mysdk_mybootclasspathfragment@1",
 			sdk_member_name: "mybootclasspathfragment",
 			prefer: false,
 			visibility: ["//visibility:public"],
@@ -450,6 +462,15 @@
 				"myapex",
 			],
 			image_name: "art",
+			contents: ["mysdk_mybootlib@1"],
+		}
+
+		java_import {
+			name: "mysdk_mybootlib@1",
+			sdk_member_name: "mybootlib",
+			visibility: ["//visibility:public"],
+			apex_available: ["com.android.art"],
+			jars: ["java/mybootlib.jar"],
 		}
 	`),
 	).RunTest(t)
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 12545d6..a13b0d7 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -564,4 +564,101 @@
 			`),
 		)
 	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=unversioned", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "unversioned",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+			`),
+		)
+	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=current", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "current",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-current.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@current",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+java_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@current",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@current"],
+}
+			`),
+		)
+	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_VERSION=2", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			preparer,
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_VERSION": "2",
+			}),
+		).RunTest(t)
+
+		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-2.zip")
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+    name: "mysdk_myjavalib@2",
+    sdk_member_name: "myjavalib",
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    jars: ["java/myjavalib.jar"],
+}
+
+sdk_snapshot {
+    name: "mysdk@2",
+    visibility: ["//visibility:public"],
+    java_header_libs: ["mysdk_myjavalib@2"],
+}
+			`),
+			// A versioned snapshot cannot be used on its own so add the source back in.
+			snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)),
+		)
+	})
 }
diff --git a/sdk/testing.go b/sdk/testing.go
index f4e85c0..3254cf9 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -131,6 +131,7 @@
 	info := &snapshotBuildInfo{
 		t:                            t,
 		r:                            result,
+		version:                      sdk.builderForTests.version,
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
@@ -236,8 +237,13 @@
 	if dir != "" {
 		dir = filepath.Clean(dir) + "/"
 	}
-	android.AssertStringEquals(t, "Snapshot zip file in wrong place",
-		fmt.Sprintf(".intermediates/%s%s/%s/%s-current.zip", dir, name, variant, name), actual)
+	suffix := ""
+	if snapshotBuildInfo.version != soongSdkSnapshotVersionUnversioned {
+		suffix = "-" + snapshotBuildInfo.version
+	}
+
+	expectedZipPath := fmt.Sprintf(".intermediates/%s%s/%s/%s%s.zip", dir, name, variant, name, suffix)
+	android.AssertStringEquals(t, "Snapshot zip file in wrong place", expectedZipPath, actual)
 
 	// Populate a mock filesystem with the files that would have been copied by
 	// the rules.
@@ -432,6 +438,11 @@
 	// The result from RunTest()
 	r *android.TestResult
 
+	// The version of the generated snapshot.
+	//
+	// See snapshotBuilder.version for more information about this field.
+	version string
+
 	// The contents of the generated Android.bp file
 	androidBpContents string
 
diff --git a/sdk/update.go b/sdk/update.go
index 85dfc4a..36b564f 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -36,6 +36,20 @@
 //     By default every unversioned module in the generated snapshot has prefer: false. Building it
 //     with SOONG_SDK_SNAPSHOT_PREFER=true will force them to use prefer: true.
 //
+// SOONG_SDK_SNAPSHOT_VERSION
+//     This provides control over the version of the generated snapshot.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=current will generate unversioned and versioned prebuilts and a
+//     versioned snapshot module. This is the default behavior. The zip file containing the
+//     generated snapshot will be <sdk-name>-current.zip.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=unversioned will generate unversioned prebuilts only and the zip
+//     file containing the generated snapshot will be <sdk-name>.zip.
+//
+//     SOONG_SDK_SNAPSHOT_VERSION=<number> will generate versioned prebuilts and a versioned
+//     snapshot module only. The zip file containing the generated snapshot will be
+//     <sdk-name>-<number>.zip.
+//
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
@@ -69,6 +83,11 @@
 		})
 )
 
+const (
+	soongSdkSnapshotVersionUnversioned = "unversioned"
+	soongSdkSnapshotVersionCurrent     = "current"
+)
+
 type generatedContents struct {
 	content     strings.Builder
 	indentLevel int
@@ -257,10 +276,26 @@
 		modules: make(map[string]*bpModule),
 	}
 
+	config := ctx.Config()
+	version := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_VERSION", "current")
+
+	// Generate versioned modules in the snapshot unless an unversioned snapshot has been requested.
+	generateVersioned := version != soongSdkSnapshotVersionUnversioned
+
+	// Generate unversioned modules in the snapshot unless a numbered snapshot has been requested.
+	//
+	// Unversioned modules are not required in that case because the numbered version will be a
+	// finalized version of the snapshot that is intended to be kept separate from the
+	generateUnversioned := version == soongSdkSnapshotVersionUnversioned || version == soongSdkSnapshotVersionCurrent
+	snapshotZipFileSuffix := ""
+	if generateVersioned {
+		snapshotZipFileSuffix = "-" + version
+	}
+
 	builder := &snapshotBuilder{
 		ctx:                   ctx,
 		sdk:                   s,
-		version:               "current",
+		version:               version,
 		snapshotDir:           snapshotDir.OutputPath,
 		copies:                make(map[string]string),
 		filesToZip:            []android.Path{bp.path},
@@ -314,20 +349,26 @@
 		// Prune any empty property sets.
 		unversioned = unversioned.transform(pruneEmptySetTransformer{})
 
-		// Copy the unversioned module so it can be modified to make it versioned.
-		versioned := unversioned.deepCopy()
+		if generateVersioned {
+			// Copy the unversioned module so it can be modified to make it versioned.
+			versioned := unversioned.deepCopy()
 
-		// Transform the unversioned module into a versioned one.
-		versioned.transform(unversionedToVersionedTransformer)
-		bpFile.AddModule(versioned)
+			// Transform the unversioned module into a versioned one.
+			versioned.transform(unversionedToVersionedTransformer)
+			bpFile.AddModule(versioned)
+		}
 
-		// Transform the unversioned module to make it suitable for use in the snapshot.
-		unversioned.transform(unversionedTransformer)
-		bpFile.AddModule(unversioned)
+		if generateUnversioned {
+			// Transform the unversioned module to make it suitable for use in the snapshot.
+			unversioned.transform(unversionedTransformer)
+			bpFile.AddModule(unversioned)
+		}
 	}
 
-	// Add the sdk/module_exports_snapshot module to the bp file.
-	s.addSnapshotModule(ctx, builder, sdkVariants, memberVariantDeps)
+	if generateVersioned {
+		// Add the sdk/module_exports_snapshot module to the bp file.
+		s.addSnapshotModule(ctx, builder, sdkVariants, memberVariantDeps)
+	}
 
 	// generate Android.bp
 	bp = newGeneratedFile(ctx, "snapshot", "Android.bp")
@@ -341,7 +382,8 @@
 	filesToZip := builder.filesToZip
 
 	// zip them all
-	outputZipFile := android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.zip").OutputPath
+	zipPath := fmt.Sprintf("%s%s.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+	outputZipFile := android.PathForModuleOut(ctx, zipPath).OutputPath
 	outputDesc := "Building snapshot for " + ctx.ModuleName()
 
 	// If there are no zips to merge then generate the output zip directly.
@@ -353,7 +395,8 @@
 		zipFile = outputZipFile
 		desc = outputDesc
 	} else {
-		zipFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"-current.unmerged.zip").OutputPath
+		intermediatePath := fmt.Sprintf("%s%s.unmerged.zip", ctx.ModuleName(), snapshotZipFileSuffix)
+		zipFile = android.PathForModuleOut(ctx, intermediatePath).OutputPath
 		desc = "Building intermediate snapshot for " + ctx.ModuleName()
 	}
 
@@ -801,9 +844,15 @@
 }
 
 type snapshotBuilder struct {
-	ctx         android.ModuleContext
-	sdk         *sdk
-	version     string
+	ctx android.ModuleContext
+	sdk *sdk
+
+	// The version of the generated snapshot.
+	//
+	// See the documentation of SOONG_SDK_SNAPSHOT_VERSION above for details of the valid values of
+	// this field.
+	version string
+
 	snapshotDir android.OutputPath
 	bpFile      *bpFile
 
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 42d5680..4805846 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -104,6 +104,12 @@
 	Recovery_available *bool
 }
 
+// Test option struct.
+type TestOptions struct {
+	// If the test is a hostside(no device required) unittest that shall be run during presubmit check.
+	Unit_test *bool
+}
+
 type TestProperties struct {
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
@@ -143,6 +149,9 @@
 	// list of device library modules that should be installed alongside the test.
 	// Only available for host sh_test modules.
 	Data_device_libs []string `android:"path,arch_variant"`
+
+	// Test options.
+	Test_options TestOptions
 }
 
 type ShBinary struct {
@@ -440,6 +449,9 @@
 					dir := strings.TrimSuffix(s.dataModules[relPath].String(), relPath)
 					entries.AddStrings("LOCAL_TEST_DATA", dir+":"+relPath)
 				}
+				if Bool(s.testProperties.Test_options.Unit_test) {
+					entries.SetBool("LOCAL_IS_UNIT_TEST", true)
+				}
 			},
 		},
 	}}
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 9e7e594..20317d8 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -3,6 +3,7 @@
 import (
 	"os"
 	"path/filepath"
+	"strconv"
 	"testing"
 
 	"android/soong/android"
@@ -148,6 +149,9 @@
 				"testdata/data1",
 				"testdata/sub/data2",
 			],
+			test_options: {
+				unit_test: true,
+			},
 		}
 	`)
 
@@ -156,6 +160,9 @@
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a sh_test_host module.")
 	}
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	actualData, _ := strconv.ParseBool(entries.EntryMap["LOCAL_IS_UNIT_TEST"][0])
+	android.AssertBoolEquals(t, "LOCAL_IS_UNIT_TEST", true, actualData)
 }
 
 func TestShTestHost_dataDeviceModules(t *testing.T) {
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 42363e9..8c8dc82 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -493,6 +493,21 @@
   [[ -e out/soong/workspace ]] || fail "Bazel workspace not created"
 }
 
+function test_bp2build_generates_fake_ninja_file {
+  setup
+  create_mock_bazel
+
+  run_bp2build
+
+  if [[ ! -f "./out/soong/build.ninja" ]]; then
+    fail "./out/soong/build.ninja was not generated"
+  fi
+
+  if ! grep "build nothing: phony" "./out/soong/build.ninja"; then
+    fail "missing phony nothing target in out/soong/build.ninja"
+  fi
+}
+
 function test_bp2build_add_android_bp {
   setup
 
@@ -678,6 +693,7 @@
 test_soong_build_rerun_iff_environment_changes
 test_dump_json_module_graph
 test_bp2build_smoke
+test_bp2build_generates_fake_ninja_file
 test_bp2build_null_build
 test_bp2build_add_android_bp
 test_bp2build_add_to_glob
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index fe0aca9..54aeda0 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -162,6 +162,8 @@
 	"OUT_DIR",
 	"AUX_OS_VARIANT_LIST",
 	"PRODUCT_SOONG_NAMESPACES",
+	"SOONG_SDK_SNAPSHOT_PREFER",
+	"SOONG_SDK_SNAPSHOT_VERSION",
 }
 
 func Banner(make_vars map[string]string) string {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index a41dbe1..cd645eb 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -155,9 +155,9 @@
 			Outputs: []string{bp2BuildMarkerFile},
 			Args:    bp2buildArgs,
 		}
-		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{
-			bp2buildInvocation,
-			mainSoongBuildInvocation,
+		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{bp2buildInvocation}
+		if config.bazelBuildMode() == mixedBuild {
+			args.PrimaryBuilderInvocations = append(args.PrimaryBuilderInvocations, mainSoongBuildInvocation)
 		}
 	} else {
 		args.PrimaryBuilderInvocations = []bootstrap.PrimaryBuilderInvocation{mainSoongBuildInvocation}