Merge "Do not enable AFDO for coverage builds" into main
diff --git a/Android.bp b/Android.bp
index c37d777..8d1280c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,7 +104,6 @@
 // Instantiate the dex_bootjars singleton module.
 dex_bootjars {
     name: "dex_bootjars",
-    no_full_install: true,
 }
 
 // Pseudo-test that's run on checkbuilds to ensure that get_clang_version can
@@ -141,3 +140,12 @@
         "//packages/modules/Virtualization/build/microdroid",
     ],
 }
+
+build_prop {
+    name: "system_ext-build.prop",
+    stem: "build.prop",
+    system_ext_specific: true,
+    product_config: ":product_config",
+    relative_install_path: "etc", // system_ext/etc/build.prop
+    visibility: ["//visibility:private"],
+}
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index 8b72f8e..4cd8dda 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -119,7 +119,10 @@
 func (a *allApexContributions) SetPrebuiltSelectionInfoProvider(ctx BaseModuleContext) {
 	addContentsToProvider := func(p *PrebuiltSelectionInfoMap, m *apexContributions) {
 		for _, content := range m.Contents() {
-			if !ctx.OtherModuleExists(content) && !ctx.Config().AllowMissingDependencies() {
+			// Verify that the module listed in contents exists in the tree
+			// Remove the prebuilt_ prefix to account for partner worksapces where the source module does not
+			// exist, and PrebuiltRenameMutator renames `prebuilt_foo` to `foo`
+			if !ctx.OtherModuleExists(content) && !ctx.OtherModuleExists(RemoveOptionalPrebuiltPrefix(content)) && !ctx.Config().AllowMissingDependencies() {
 				ctx.ModuleErrorf("%s listed in apex_contributions %s does not exist\n", content, m.Name())
 			}
 			pi := &PrebuiltSelectionInfo{
diff --git a/android/build_prop.go b/android/build_prop.go
index 14e5e23..d48d94d 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -37,6 +37,9 @@
 
 	// Path to a JSON file containing product configs.
 	Product_config *string `android:"path"`
+
+	// Optional subdirectory under which this file is installed into
+	Relative_install_path *string
 }
 
 type buildPropModule struct {
@@ -56,6 +59,8 @@
 	partition := p.PartitionTag(ctx.DeviceConfig())
 	if partition == "system" {
 		return ctx.Config().SystemPropFiles(ctx)
+	} else if partition == "system_ext" {
+		return ctx.Config().SystemExtPropFiles(ctx)
 	}
 	return nil
 }
@@ -84,8 +89,8 @@
 	}
 
 	partition := p.PartitionTag(ctx.DeviceConfig())
-	if partition != "system" {
-		ctx.PropertyErrorf("partition", "unsupported partition %q: only \"system\" is supported", partition)
+	if partition != "system" && partition != "system_ext" {
+		ctx.PropertyErrorf("partition", "unsupported partition %q: only \"system\" and \"system_ext\" are supported", partition)
 		return
 	}
 
@@ -134,7 +139,7 @@
 
 	rule.Build(ctx.ModuleName(), "generating build.prop")
 
-	p.installPath = PathForModuleInstall(ctx)
+	p.installPath = PathForModuleInstall(ctx, proptools.String(p.properties.Relative_install_path))
 	ctx.InstallFile(p.installPath, p.stem(), p.outputFilePath)
 
 	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
diff --git a/android/config.go b/android/config.go
index 40a73d6..3b30af8 100644
--- a/android/config.go
+++ b/android/config.go
@@ -343,9 +343,6 @@
 	// modules that aren't mixed-built for at least one variant will cause a build
 	// failure
 	ensureAllowlistIntegrity bool
-
-	// List of Api libraries that contribute to Api surfaces.
-	apiLibraries map[string]struct{}
 }
 
 type deviceConfig struct {
@@ -595,40 +592,6 @@
 	setBuildMode(cmdArgs.ModuleGraphFile, GenerateModuleGraph)
 	setBuildMode(cmdArgs.DocFile, GenerateDocFile)
 
-	// TODO(b/276958307): Replace the hardcoded list to a sdk_library local prop.
-	config.apiLibraries = map[string]struct{}{
-		"android.net.ipsec.ike":             {},
-		"art.module.public.api":             {},
-		"conscrypt.module.public.api":       {},
-		"framework-adservices":              {},
-		"framework-appsearch":               {},
-		"framework-bluetooth":               {},
-		"framework-configinfrastructure":    {},
-		"framework-connectivity":            {},
-		"framework-connectivity-t":          {},
-		"framework-devicelock":              {},
-		"framework-graphics":                {},
-		"framework-healthfitness":           {},
-		"framework-location":                {},
-		"framework-media":                   {},
-		"framework-mediaprovider":           {},
-		"framework-nfc":                     {},
-		"framework-ondevicepersonalization": {},
-		"framework-pdf":                     {},
-		"framework-pdf-v":                   {},
-		"framework-permission":              {},
-		"framework-permission-s":            {},
-		"framework-scheduling":              {},
-		"framework-sdkextensions":           {},
-		"framework-statsd":                  {},
-		"framework-sdksandbox":              {},
-		"framework-tethering":               {},
-		"framework-uwb":                     {},
-		"framework-virtualization":          {},
-		"framework-wifi":                    {},
-		"i18n.module.public.api":            {},
-	}
-
 	config.productVariables.Build_from_text_stub = boolPtr(config.BuildFromTextStub())
 
 	return Config{config}, err
@@ -1979,17 +1942,6 @@
 	c.productVariables.Build_from_text_stub = boolPtr(b)
 }
 
-func (c *config) SetApiLibraries(libs []string) {
-	c.apiLibraries = make(map[string]struct{})
-	for _, lib := range libs {
-		c.apiLibraries[lib] = struct{}{}
-	}
-}
-
-func (c *config) GetApiLibraries() map[string]struct{} {
-	return c.apiLibraries
-}
-
 func (c *deviceConfig) CheckVendorSeappViolations() bool {
 	return Bool(c.config.productVariables.CheckVendorSeappViolations)
 }
@@ -2004,41 +1956,41 @@
 }
 
 var (
-	mainlineApexContributionBuildFlags = []string{
-		"RELEASE_APEX_CONTRIBUTIONS_ADBD",
-		"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES",
-		"RELEASE_APEX_CONTRIBUTIONS_APPSEARCH",
-		"RELEASE_APEX_CONTRIBUTIONS_ART",
-		"RELEASE_APEX_CONTRIBUTIONS_BLUETOOTH",
-		"RELEASE_APEX_CONTRIBUTIONS_CAPTIVEPORTALLOGIN",
-		"RELEASE_APEX_CONTRIBUTIONS_CELLBROADCAST",
-		"RELEASE_APEX_CONTRIBUTIONS_CONFIGINFRASTRUCTURE",
-		"RELEASE_APEX_CONTRIBUTIONS_CONNECTIVITY",
-		"RELEASE_APEX_CONTRIBUTIONS_CONSCRYPT",
-		"RELEASE_APEX_CONTRIBUTIONS_CRASHRECOVERY",
-		"RELEASE_APEX_CONTRIBUTIONS_DEVICELOCK",
-		"RELEASE_APEX_CONTRIBUTIONS_DOCUMENTSUIGOOGLE",
-		"RELEASE_APEX_CONTRIBUTIONS_EXTSERVICES",
-		"RELEASE_APEX_CONTRIBUTIONS_HEALTHFITNESS",
-		"RELEASE_APEX_CONTRIBUTIONS_IPSEC",
-		"RELEASE_APEX_CONTRIBUTIONS_MEDIA",
-		"RELEASE_APEX_CONTRIBUTIONS_MEDIAPROVIDER",
-		"RELEASE_APEX_CONTRIBUTIONS_MODULE_METADATA",
-		"RELEASE_APEX_CONTRIBUTIONS_NETWORKSTACKGOOGLE",
-		"RELEASE_APEX_CONTRIBUTIONS_NEURALNETWORKS",
-		"RELEASE_APEX_CONTRIBUTIONS_ONDEVICEPERSONALIZATION",
-		"RELEASE_APEX_CONTRIBUTIONS_PERMISSION",
-		"RELEASE_APEX_CONTRIBUTIONS_PRIMARY_LIBS",
-		"RELEASE_APEX_CONTRIBUTIONS_REMOTEKEYPROVISIONING",
-		"RELEASE_APEX_CONTRIBUTIONS_RESOLV",
-		"RELEASE_APEX_CONTRIBUTIONS_SCHEDULING",
-		"RELEASE_APEX_CONTRIBUTIONS_SDKEXTENSIONS",
-		"RELEASE_APEX_CONTRIBUTIONS_SWCODEC",
-		"RELEASE_APEX_CONTRIBUTIONS_STATSD",
-		"RELEASE_APEX_CONTRIBUTIONS_TELEMETRY_TVP",
-		"RELEASE_APEX_CONTRIBUTIONS_TZDATA",
-		"RELEASE_APEX_CONTRIBUTIONS_UWB",
-		"RELEASE_APEX_CONTRIBUTIONS_WIFI",
+	mainlineApexContributionBuildFlagsToApexNames = map[string]string{
+		"RELEASE_APEX_CONTRIBUTIONS_ADBD":                    "com.android.adbd",
+		"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES":              "com.android.adservices",
+		"RELEASE_APEX_CONTRIBUTIONS_APPSEARCH":               "com.android.appsearch",
+		"RELEASE_APEX_CONTRIBUTIONS_ART":                     "com.android.art",
+		"RELEASE_APEX_CONTRIBUTIONS_BLUETOOTH":               "com.android.btservices",
+		"RELEASE_APEX_CONTRIBUTIONS_CAPTIVEPORTALLOGIN":      "",
+		"RELEASE_APEX_CONTRIBUTIONS_CELLBROADCAST":           "com.android.cellbroadcast",
+		"RELEASE_APEX_CONTRIBUTIONS_CONFIGINFRASTRUCTURE":    "com.android.configinfrastructure",
+		"RELEASE_APEX_CONTRIBUTIONS_CONNECTIVITY":            "com.android.tethering",
+		"RELEASE_APEX_CONTRIBUTIONS_CONSCRYPT":               "com.android.conscrypt",
+		"RELEASE_APEX_CONTRIBUTIONS_CRASHRECOVERY":           "",
+		"RELEASE_APEX_CONTRIBUTIONS_DEVICELOCK":              "com.android.devicelock",
+		"RELEASE_APEX_CONTRIBUTIONS_DOCUMENTSUIGOOGLE":       "",
+		"RELEASE_APEX_CONTRIBUTIONS_EXTSERVICES":             "com.android.extservices",
+		"RELEASE_APEX_CONTRIBUTIONS_HEALTHFITNESS":           "com.android.healthfitness",
+		"RELEASE_APEX_CONTRIBUTIONS_IPSEC":                   "com.android.ipsec",
+		"RELEASE_APEX_CONTRIBUTIONS_MEDIA":                   "com.android.media",
+		"RELEASE_APEX_CONTRIBUTIONS_MEDIAPROVIDER":           "com.android.mediaprovider",
+		"RELEASE_APEX_CONTRIBUTIONS_MODULE_METADATA":         "",
+		"RELEASE_APEX_CONTRIBUTIONS_NETWORKSTACKGOOGLE":      "",
+		"RELEASE_APEX_CONTRIBUTIONS_NEURALNETWORKS":          "com.android.neuralnetworks",
+		"RELEASE_APEX_CONTRIBUTIONS_ONDEVICEPERSONALIZATION": "com.android.ondevicepersonalization",
+		"RELEASE_APEX_CONTRIBUTIONS_PERMISSION":              "com.android.permission",
+		"RELEASE_APEX_CONTRIBUTIONS_PRIMARY_LIBS":            "",
+		"RELEASE_APEX_CONTRIBUTIONS_REMOTEKEYPROVISIONING":   "com.android.rkpd",
+		"RELEASE_APEX_CONTRIBUTIONS_RESOLV":                  "com.android.resolv",
+		"RELEASE_APEX_CONTRIBUTIONS_SCHEDULING":              "com.android.scheduling",
+		"RELEASE_APEX_CONTRIBUTIONS_SDKEXTENSIONS":           "com.android.sdkext",
+		"RELEASE_APEX_CONTRIBUTIONS_SWCODEC":                 "com.android.media.swcodec",
+		"RELEASE_APEX_CONTRIBUTIONS_STATSD":                  "com.android.os.statsd",
+		"RELEASE_APEX_CONTRIBUTIONS_TELEMETRY_TVP":           "",
+		"RELEASE_APEX_CONTRIBUTIONS_TZDATA":                  "com.android.tzdata",
+		"RELEASE_APEX_CONTRIBUTIONS_UWB":                     "com.android.uwb",
+		"RELEASE_APEX_CONTRIBUTIONS_WIFI":                    "com.android.wifi",
 	}
 )
 
@@ -2046,7 +1998,7 @@
 // Each mainline module will have one entry in the list
 func (c *config) AllApexContributions() []string {
 	ret := []string{}
-	for _, f := range mainlineApexContributionBuildFlags {
+	for _, f := range SortedKeys(mainlineApexContributionBuildFlagsToApexNames) {
 		if val, exists := c.GetBuildFlag(f); exists && val != "" {
 			ret = append(ret, val)
 		}
@@ -2054,6 +2006,10 @@
 	return ret
 }
 
+func (c *config) AllMainlineApexNames() []string {
+	return SortedStringValues(mainlineApexContributionBuildFlagsToApexNames)
+}
+
 func (c *config) BuildIgnoreApexContributionContents() *bool {
 	return c.productVariables.BuildIgnoreApexContributionContents
 }
@@ -2086,6 +2042,10 @@
 	return PathsForSource(ctx, c.productVariables.SystemPropFiles)
 }
 
+func (c *config) SystemExtPropFiles(ctx PathContext) Paths {
+	return PathsForSource(ctx, c.productVariables.SystemExtPropFiles)
+}
+
 func (c *config) EnableUffdGc() string {
 	return String(c.productVariables.EnableUffdGc)
 }
diff --git a/android/paths.go b/android/paths.go
index dad70f7..d20b84a 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -237,6 +237,9 @@
 	// directory, and OutputPath.Join("foo").Rel() would return "foo".
 	Rel() string
 
+	// WithoutRel returns a new Path with no relative path, i.e. Rel() will return the same value as Base().
+	WithoutRel() Path
+
 	// RelativeToTop returns a new path relative to the top, it is provided solely for use in tests.
 	//
 	// It is guaranteed to always return the same type as it is called on, e.g. if called on an
@@ -1119,6 +1122,11 @@
 	return p
 }
 
+func (p basePath) withoutRel() basePath {
+	p.rel = filepath.Base(p.path)
+	return p
+}
+
 // SourcePath is a Path representing a file path rooted from SrcDir
 type SourcePath struct {
 	basePath
@@ -1278,6 +1286,11 @@
 	return p.path
 }
 
+func (p SourcePath) WithoutRel() Path {
+	p.basePath = p.basePath.withoutRel()
+	return p
+}
+
 // Join creates a new SourcePath with paths... joined with the current path. The
 // provided paths... may not use '..' to escape from the current path.
 func (p SourcePath) Join(ctx PathContext, paths ...string) SourcePath {
@@ -1362,8 +1375,8 @@
 	return p
 }
 
-func (p OutputPath) WithoutRel() OutputPath {
-	p.basePath.rel = filepath.Base(p.basePath.path)
+func (p OutputPath) WithoutRel() Path {
+	p.basePath = p.basePath.withoutRel()
 	return p
 }
 
@@ -1399,6 +1412,11 @@
 	basePath
 }
 
+func (t toolDepPath) WithoutRel() Path {
+	t.basePath = t.basePath.withoutRel()
+	return t
+}
+
 func (t toolDepPath) RelativeToTop() Path {
 	ensureTestOnly()
 	return t
@@ -1767,6 +1785,11 @@
 	return p
 }
 
+func (p InstallPath) WithoutRel() Path {
+	p.basePath = p.basePath.withoutRel()
+	return p
+}
+
 func (p InstallPath) getSoongOutDir() string {
 	return p.soongOutDir
 }
@@ -2087,6 +2110,11 @@
 	return p
 }
 
+func (p PhonyPath) WithoutRel() Path {
+	p.basePath = p.basePath.withoutRel()
+	return p
+}
+
 func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
 	panic("Not implemented")
 }
@@ -2103,6 +2131,11 @@
 	return p
 }
 
+func (p testPath) WithoutRel() Path {
+	p.basePath = p.basePath.withoutRel()
+	return p
+}
+
 func (p testPath) String() string {
 	return p.path
 }
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 8856d26..9c8c130 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -419,15 +419,7 @@
 // The metadata will be used for source vs prebuilts selection
 func PrebuiltSourceDepsMutator(ctx BottomUpMutatorContext) {
 	m := ctx.Module()
-	// If this module is a prebuilt, is enabled and has not been renamed to source then add a
-	// dependency onto the source if it is present.
-	if p := GetEmbeddedPrebuilt(m); p != nil && m.Enabled(ctx) && !p.properties.PrebuiltRenamedToSource {
-		bmn, _ := m.(baseModuleName)
-		name := bmn.BaseModuleName()
-		if ctx.OtherModuleReverseDependencyVariantExists(name) {
-			ctx.AddReverseDependency(ctx.Module(), PrebuiltDepTag, name)
-			p.properties.SourceExists = true
-		}
+	if p := GetEmbeddedPrebuilt(m); p != nil {
 		// Add a dependency from the prebuilt to the `all_apex_contributions`
 		// metadata module
 		// TODO: When all branches contain this singleton module, make this strict
@@ -435,7 +427,16 @@
 		if ctx.OtherModuleExists("all_apex_contributions") {
 			ctx.AddDependency(m, AcDepTag, "all_apex_contributions")
 		}
-
+		if m.Enabled(ctx) && !p.properties.PrebuiltRenamedToSource {
+			// If this module is a prebuilt, is enabled and has not been renamed to source then add a
+			// dependency onto the source if it is present.
+			bmn, _ := m.(baseModuleName)
+			name := bmn.BaseModuleName()
+			if ctx.OtherModuleReverseDependencyVariantExists(name) {
+				ctx.AddReverseDependency(ctx.Module(), PrebuiltDepTag, name)
+				p.properties.SourceExists = true
+			}
+		}
 	}
 }
 
@@ -664,12 +665,37 @@
 	return p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0
 }
 
+type apexVariationName interface {
+	ApexVariationName() string
+}
+
 // usePrebuilt returns true if a prebuilt should be used instead of the source module.  The prebuilt
 // will be used if it is marked "prefer" or if the source module is disabled.
 func (p *Prebuilt) usePrebuilt(ctx BaseMutatorContext, source Module, prebuilt Module) bool {
+	isMainlinePrebuilt := func(prebuilt Module) bool {
+		apex, ok := prebuilt.(apexVariationName)
+		if !ok {
+			return false
+		}
+		// Prebuilts of aosp apexes in prebuilts/runtime
+		// Used in minimal art branches
+		if prebuilt.base().BaseModuleName() == apex.ApexVariationName() {
+			return false
+		}
+		return InList(apex.ApexVariationName(), ctx.Config().AllMainlineApexNames())
+	}
+
 	// Use `all_apex_contributions` for source vs prebuilt selection.
 	psi := PrebuiltSelectionInfoMap{}
-	ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(am Module) {
+	var psiDepTag blueprint.DependencyTag
+	if p := GetEmbeddedPrebuilt(ctx.Module()); p != nil {
+		// This is a prebuilt module, visit all_apex_contributions to get the info
+		psiDepTag = AcDepTag
+	} else {
+		// This is a source module, visit any of its prebuilts to get the info
+		psiDepTag = PrebuiltDepTag
+	}
+	ctx.VisitDirectDepsWithTag(psiDepTag, func(am Module) {
 		psi, _ = OtherModuleProvider(ctx, am, PrebuiltSelectionInfoProvider)
 	})
 
@@ -682,6 +708,11 @@
 		return true
 	}
 
+	// If this is a mainline prebuilt, but has not been flagged, hide it.
+	if isMainlinePrebuilt(prebuilt) {
+		return false
+	}
+
 	// If the baseModuleName could not be found in the metadata module,
 	// fall back to the existing source vs prebuilt selection.
 	// TODO: Drop the fallback mechanisms
diff --git a/android/sbom.go b/android/sbom.go
index dd2d2fa..2a5499e 100644
--- a/android/sbom.go
+++ b/android/sbom.go
@@ -42,7 +42,9 @@
 }
 
 // sbomSingleton is used to generate build actions of generating SBOM of products.
-type sbomSingleton struct{}
+type sbomSingleton struct {
+	sbomFile OutputPath
+}
 
 func sbomSingletonFactory() Singleton {
 	return &sbomSingleton{}
@@ -77,12 +79,12 @@
 	implicits = append(implicits, installedFilesStamp)
 
 	metadataDb := PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "compliance-metadata.db")
-	sbomFile := PathForOutput(ctx, "sbom", ctx.Config().DeviceProduct(), "sbom.spdx.json")
+	this.sbomFile = PathForOutput(ctx, "sbom", ctx.Config().DeviceProduct(), "sbom.spdx.json")
 	ctx.Build(pctx, BuildParams{
 		Rule:      genSbomRule,
 		Input:     metadataDb,
 		Implicits: implicits,
-		Output:    sbomFile,
+		Output:    this.sbomFile,
 		Args: map[string]string{
 			"productOut":           filepath.Join(ctx.Config().OutDir(), "target", "product", String(prodVars.DeviceName)),
 			"soongOut":             ctx.Config().soongOutDir,
@@ -91,10 +93,19 @@
 		},
 	})
 
-	// Phony rule "soong-sbom". "m soong-sbom" to generate product SBOM in Soong.
-	ctx.Build(pctx, BuildParams{
-		Rule:   blueprint.Phony,
-		Inputs: []Path{sbomFile},
-		Output: PathForPhony(ctx, "soong-sbom"),
-	})
+	if !ctx.Config().UnbundledBuildApps() {
+		// When building SBOM of products, phony rule "sbom" is for generating product SBOM in Soong.
+		ctx.Build(pctx, BuildParams{
+			Rule:   blueprint.Phony,
+			Inputs: []Path{this.sbomFile},
+			Output: PathForPhony(ctx, "sbom"),
+		})
+	}
+}
+
+func (this *sbomSingleton) MakeVars(ctx MakeVarsContext) {
+	// When building SBOM of products
+	if !ctx.Config().UnbundledBuildApps() {
+		ctx.DistForGoalWithFilename("droid", this.sbomFile, "sbom/sbom.spdx.json")
+	}
 }
diff --git a/android/variable.go b/android/variable.go
index a715e0e..c9b29f1 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -508,7 +508,8 @@
 
 	ArtTargetIncludeDebugBuild *bool `json:",omitempty"`
 
-	SystemPropFiles []string `json:",omitempty"`
+	SystemPropFiles    []string `json:",omitempty"`
+	SystemExtPropFiles []string `json:",omitempty"`
 
 	EnableUffdGc *string `json:",omitempty"`
 }
diff --git a/apex/apex_test.go b/apex/apex_test.go
index cdf16dd..852f5fb 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -10016,9 +10016,6 @@
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.PrepareForTestWithJacocoInstrumentation,
 		java.FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 		android.FixtureMergeMockFs(fs),
 	).RunTestWithBp(t, bp)
 
@@ -11465,6 +11462,118 @@
 	}
 }
 
+// Test that product packaging installs the selected mainline module in workspaces withtout source mainline module
+func TestInstallationRulesForMultipleApexPrebuiltsWithoutSource(t *testing.T) {
+	// for a mainline module family, check that only the flagged soong module is visible to make
+	checkHideFromMake := func(t *testing.T, ctx *android.TestContext, visibleModuleNames []string, hiddenModuleNames []string) {
+		variation := func(moduleName string) string {
+			ret := "android_common_com.android.adservices"
+			if moduleName == "com.google.android.foo" {
+				ret = "android_common_com.google.android.foo_com.google.android.foo"
+			}
+			return ret
+		}
+
+		for _, visibleModuleName := range visibleModuleNames {
+			visibleModule := ctx.ModuleForTests(visibleModuleName, variation(visibleModuleName)).Module()
+			android.AssertBoolEquals(t, "Apex "+visibleModuleName+" selected using apex_contributions should be visible to make", false, visibleModule.IsHideFromMake())
+		}
+
+		for _, hiddenModuleName := range hiddenModuleNames {
+			hiddenModule := ctx.ModuleForTests(hiddenModuleName, variation(hiddenModuleName)).Module()
+			android.AssertBoolEquals(t, "Apex "+hiddenModuleName+" not selected using apex_contributions should be hidden from make", true, hiddenModule.IsHideFromMake())
+
+		}
+	}
+
+	bp := `
+		apex_key {
+			name: "com.android.adservices.key",
+			public_key: "com.android.adservices.avbpubkey",
+			private_key: "com.android.adservices.pem",
+		}
+
+		// AOSP source apex
+		apex {
+			name: "com.android.adservices",
+			key: "com.android.adservices.key",
+			updatable: false,
+		}
+
+		// Prebuilt Google APEX.
+
+		prebuilt_apex {
+			name: "com.google.android.adservices",
+			apex_name: "com.android.adservices",
+			src: "com.android.foo-arm.apex",
+		}
+
+		// Another Prebuilt Google APEX
+		prebuilt_apex {
+			name: "com.google.android.adservices.v2",
+			apex_name: "com.android.adservices",
+			src: "com.android.foo-arm.apex",
+		}
+
+		// APEX contribution modules
+
+
+		apex_contributions {
+			name: "adservices.prebuilt.contributions",
+			api_domain: "com.android.adservices",
+			contents: ["prebuilt_com.google.android.adservices"],
+		}
+
+		apex_contributions {
+			name: "adservices.prebuilt.v2.contributions",
+			api_domain: "com.android.adservices",
+			contents: ["prebuilt_com.google.android.adservices.v2"],
+		}
+	`
+
+	testCases := []struct {
+		desc                       string
+		selectedApexContributions  string
+		expectedVisibleModuleNames []string
+		expectedHiddenModuleNames  []string
+	}{
+		{
+			desc:                       "No apex contributions selected, source aosp apex should be visible, and mainline prebuilts should be hidden",
+			selectedApexContributions:  "",
+			expectedVisibleModuleNames: []string{"com.android.adservices"},
+			expectedHiddenModuleNames:  []string{"com.google.android.adservices", "com.google.android.adservices.v2"},
+		},
+		{
+			desc:                       "Prebuilt apex prebuilt_com.android.foo is selected",
+			selectedApexContributions:  "adservices.prebuilt.contributions",
+			expectedVisibleModuleNames: []string{"com.android.adservices", "com.google.android.adservices"},
+			expectedHiddenModuleNames:  []string{"com.google.android.adservices.v2"},
+		},
+		{
+			desc:                       "Prebuilt apex prebuilt_com.android.foo.v2 is selected",
+			selectedApexContributions:  "adservices.prebuilt.v2.contributions",
+			expectedVisibleModuleNames: []string{"com.android.adservices", "com.google.android.adservices.v2"},
+			expectedHiddenModuleNames:  []string{"com.google.android.adservices"},
+		},
+	}
+
+	for _, tc := range testCases {
+		preparer := android.GroupFixturePreparers(
+			android.FixtureMergeMockFs(map[string][]byte{
+				"system/sepolicy/apex/com.android.adservices-file_contexts": nil,
+			}),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.BuildFlags = map[string]string{
+					"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": tc.selectedApexContributions,
+				}
+			}),
+		)
+		ctx := testApex(t, bp, preparer)
+
+		checkHideFromMake(t, ctx, tc.expectedVisibleModuleNames, tc.expectedHiddenModuleNames)
+	}
+}
+
 func TestAconfifDeclarationsValidation(t *testing.T) {
 	aconfigDeclarationLibraryString := func(moduleNames []string) (ret string) {
 		for _, moduleName := range moduleNames {
@@ -11490,9 +11599,6 @@
 		prepareForApexTest,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 	).RunTestWithBp(t, `
 		java_library {
 			name: "baz-java-lib",
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 919cb01..533f937 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -561,6 +561,7 @@
 		result := preparers.RunTestWithBp(t, fmt.Sprintf(bp, "enabled: false,"))
 
 		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+			`all_apex_contributions`,
 			`dex2oatd`,
 			`prebuilt_art-bootclasspath-fragment`,
 			`prebuilt_com.android.art.apex.selector`,
@@ -568,6 +569,7 @@
 		})
 
 		java.CheckModuleDependencies(t, result.TestContext, "art-bootclasspath-fragment", "android_common_com.android.art", []string{
+			`all_apex_contributions`,
 			`dex2oatd`,
 			`prebuilt_bar`,
 			`prebuilt_com.android.art.deapexer`,
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index f6c53b2..452a43e 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -274,6 +274,7 @@
 	ctx := result.TestContext
 
 	java.CheckModuleDependencies(t, ctx, "myapex", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`dex2oatd`,
 		`prebuilt_myapex.apex.selector`,
 		`prebuilt_myapex.deapexer`,
@@ -281,6 +282,7 @@
 	})
 
 	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 		`prebuilt_myapex.deapexer`,
@@ -432,6 +434,7 @@
 	ctx := result.TestContext
 
 	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 		`prebuilt_myapex.deapexer`,
diff --git a/java/aar.go b/java/aar.go
index 186289e..c8563c8 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -166,7 +166,11 @@
 func (a *aapt) useResourceProcessorBusyBox(ctx android.BaseModuleContext) bool {
 	return BoolDefault(a.aaptProperties.Use_resource_processor, ctx.Config().UseResourceProcessorByDefault()) &&
 		// TODO(b/331641946): remove this when ResourceProcessorBusyBox supports generating shared libraries.
-		!slices.Contains(a.aaptProperties.Aaptflags, "--shared-lib")
+		!slices.Contains(a.aaptProperties.Aaptflags, "--shared-lib") &&
+		// Use the legacy resource processor in kythe builds.
+		// The legacy resource processor creates an R.srcjar, which kythe can use for generating crossrefs.
+		// TODO(b/354854007): Re-enable BusyBox in kythe builds
+		!ctx.Config().EmitXrefRules()
 }
 
 func (a *aapt) filterProduct() string {
@@ -991,17 +995,17 @@
 
 	properties AARImportProperties
 
-	headerJarFile                      android.WritablePath
-	implementationJarFile              android.WritablePath
-	implementationAndResourcesJarFile  android.WritablePath
-	proguardFlags                      android.WritablePath
-	exportPackage                      android.WritablePath
+	headerJarFile                      android.Path
+	implementationJarFile              android.Path
+	implementationAndResourcesJarFile  android.Path
+	proguardFlags                      android.Path
+	exportPackage                      android.Path
 	transitiveAaptResourcePackagesFile android.Path
-	extraAaptPackagesFile              android.WritablePath
+	extraAaptPackagesFile              android.Path
 	manifest                           android.Path
-	assetsPackage                      android.WritablePath
-	rTxt                               android.WritablePath
-	rJar                               android.WritablePath
+	assetsPackage                      android.Path
+	rTxt                               android.Path
+	rJar                               android.Path
 
 	resourcesNodesDepSet *android.DepSet[*resourcesNode]
 	manifestsDepSet      *android.DepSet[android.Path]
@@ -1166,14 +1170,14 @@
 		a.manifest = extractedManifest
 	}
 
-	a.rTxt = extractedAARDir.Join(ctx, "R.txt")
-	a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
-	a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
+	rTxt := extractedAARDir.Join(ctx, "R.txt")
+	assetsPackage := android.PathForModuleOut(ctx, "assets.zip")
+	proguardFlags := extractedAARDir.Join(ctx, "proguard.txt")
 	transitiveProguardFlags, transitiveUnconditionalExportedFlags := collectDepProguardSpecInfo(ctx)
 	android.SetProvider(ctx, ProguardSpecInfoProvider, ProguardSpecInfo{
 		ProguardFlagsFiles: android.NewDepSet[android.Path](
 			android.POSTORDER,
-			android.Paths{a.proguardFlags},
+			android.Paths{proguardFlags},
 			transitiveProguardFlags,
 		),
 		UnconditionallyExportedProguardFlags: android.NewDepSet[android.Path](
@@ -1186,15 +1190,19 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        unzipAAR,
 		Input:       a.aarPath,
-		Outputs:     android.WritablePaths{classpathFile, a.proguardFlags, extractedManifest, a.assetsPackage, a.rTxt},
+		Outputs:     android.WritablePaths{classpathFile, proguardFlags, extractedManifest, assetsPackage, rTxt},
 		Description: "unzip AAR",
 		Args: map[string]string{
 			"outDir":             extractedAARDir.String(),
 			"combinedClassesJar": classpathFile.String(),
-			"assetsPackage":      a.assetsPackage.String(),
+			"assetsPackage":      assetsPackage.String(),
 		},
 	})
 
+	a.proguardFlags = proguardFlags
+	a.assetsPackage = assetsPackage
+	a.rTxt = rTxt
+
 	// Always set --pseudo-localize, it will be stripped out later for release
 	// builds that don't want it.
 	compileFlags := []string{"--pseudo-localize"}
@@ -1202,10 +1210,10 @@
 	flata := compiledResDir.Join(ctx, "gen_res.flata")
 	aapt2CompileZip(ctx, flata, a.aarPath, "res", compileFlags)
 
-	a.exportPackage = android.PathForModuleOut(ctx, "package-res.apk")
+	exportPackage := android.PathForModuleOut(ctx, "package-res.apk")
 	proguardOptionsFile := android.PathForModuleGen(ctx, "proguard.options")
 	aaptRTxt := android.PathForModuleOut(ctx, "R.txt")
-	a.extraAaptPackagesFile = android.PathForModuleOut(ctx, "extra_packages")
+	extraAaptPackagesFile := android.PathForModuleOut(ctx, "extra_packages")
 
 	var linkDeps android.Paths
 
@@ -1241,13 +1249,16 @@
 	}
 
 	transitiveAssets := android.ReverseSliceInPlace(staticDeps.assets())
-	aapt2Link(ctx, a.exportPackage, nil, proguardOptionsFile, aaptRTxt,
+	aapt2Link(ctx, exportPackage, nil, proguardOptionsFile, aaptRTxt,
 		linkFlags, linkDeps, nil, overlayRes, transitiveAssets, nil, nil)
+	a.exportPackage = exportPackage
 
-	a.rJar = android.PathForModuleOut(ctx, "busybox/R.jar")
-	resourceProcessorBusyBoxGenerateBinaryR(ctx, a.rTxt, a.manifest, a.rJar, nil, true, nil, false)
+	rJar := android.PathForModuleOut(ctx, "busybox/R.jar")
+	resourceProcessorBusyBoxGenerateBinaryR(ctx, a.rTxt, a.manifest, rJar, nil, true, nil, false)
+	a.rJar = rJar
 
-	aapt2ExtractExtraPackages(ctx, a.extraAaptPackagesFile, a.rJar)
+	aapt2ExtractExtraPackages(ctx, extraAaptPackagesFile, a.rJar)
+	a.extraAaptPackagesFile = extraAaptPackagesFile
 
 	resourcesNodesDepSetBuilder := android.NewDepSetBuilder[*resourcesNode](android.TOPOLOGICAL)
 	resourcesNodesDepSetBuilder.Direct(&resourcesNode{
@@ -1330,13 +1341,14 @@
 
 	if len(staticHeaderJars) > 0 {
 		combineJars := append(android.Paths{classpathFile}, staticHeaderJars...)
-		a.headerJarFile = android.PathForModuleOut(ctx, "turbine-combined", jarName)
-		TransformJarsToJar(ctx, a.headerJarFile, "combine header jars", combineJars, android.OptionalPath{}, false, nil, nil)
+		headerJarFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
+		TransformJarsToJar(ctx, headerJarFile, "combine header jars", combineJars, android.OptionalPath{}, false, nil, nil)
+		a.headerJarFile = headerJarFile
 	} else {
 		a.headerJarFile = classpathFile
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(a.headerJarFile),
 		ResourceJars:                   android.PathsIfNonNil(resourceJarFile),
 		TransitiveLibsHeaderJars:       a.transitiveLibsHeaderJars,
@@ -1436,3 +1448,7 @@
 	InitJavaModuleMultiTargets(module, android.DeviceSupported)
 	return module
 }
+
+func (a *AARImport) IDEInfo(dpInfo *android.IdeInfo) {
+	dpInfo.Jars = append(dpInfo.Jars, a.headerJarFile.String(), a.rJar.String())
+}
diff --git a/java/app_set.go b/java/app_set.go
index 33d3ade..7997570 100644
--- a/java/app_set.go
+++ b/java/app_set.go
@@ -35,7 +35,7 @@
 
 type AndroidAppSetProperties struct {
 	// APK Set path
-	Set *string
+	Set *string `android:"path"`
 
 	// Specifies that this app should be installed to the priv-app directory,
 	// where the system will grant it additional privileges not available to
diff --git a/java/base.go b/java/base.go
index 02df147..65a8b30 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1228,14 +1228,17 @@
 			ctx.ModuleErrorf("headers_only is enabled but Turbine is disabled.")
 		}
 
-		_, j.headerJarFile, _ =
-			j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
-				extraCombinedJars)
+		_, combinedHeaderJarFile := j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
+			extraCombinedJars)
+
+		combinedHeaderJarFile = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		combinedHeaderJarFile = j.repackageFlagsIfNecessary(ctx, combinedHeaderJarFile, jarName, "repackage-turbine")
 		if ctx.Failed() {
 			return
 		}
+		j.headerJarFile = combinedHeaderJarFile
 
-		android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+		android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 			HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
 			TransitiveLibsHeaderJars:            j.transitiveLibsHeaderJars,
 			TransitiveStaticLibsHeaderJars:      j.transitiveStaticLibsHeaderJars,
@@ -1311,7 +1314,7 @@
 			return
 		}
 
-		kotlinJarPath := j.repackageFlagsIfNecessary(ctx, kotlinJar.OutputPath, jarName, "kotlinc")
+		kotlinJarPath := j.repackageFlagsIfNecessary(ctx, kotlinJar, jarName, "kotlinc")
 
 		// Make javac rule depend on the kotlinc rule
 		flags.classpath = append(classpath{kotlinHeaderJar}, flags.classpath...)
@@ -1347,11 +1350,12 @@
 			// with sharding enabled. See: b/77284273.
 		}
 		extraJars := append(slices.Clone(kotlinHeaderJars), extraCombinedJars...)
-		headerJarFileWithoutDepsOrJarjar, j.headerJarFile, j.repackagedHeaderJarFile =
+		var combinedHeaderJarFile android.Path
+		headerJarFileWithoutDepsOrJarjar, combinedHeaderJarFile =
 			j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName, extraJars)
-		if ctx.Failed() {
-			return
-		}
+
+		j.headerJarFile = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		j.repackagedHeaderJarFile = j.repackageFlagsIfNecessary(ctx, j.headerJarFile, jarName, "turbine")
 	}
 	if len(uniqueJavaFiles) > 0 || len(srcJars) > 0 {
 		hasErrorproneableFiles := false
@@ -1513,7 +1517,7 @@
 
 	// Combine the classes built from sources, any manifests, and any static libraries into
 	// classes.jar. If there is only one input jar this step will be skipped.
-	var outputFile android.OutputPath
+	var outputFile android.Path
 
 	if len(jars) == 1 && !manifest.Valid() {
 		// Optimization: skip the combine step as there is nothing to do
@@ -1529,49 +1533,36 @@
 		// to the copy rules.
 		stub, _ := moduleStubLinkType(ctx.ModuleName())
 
-		// Transform the single path to the jar into an OutputPath as that is required by the following
-		// code.
-		if moduleOutPath, ok := jars[0].(android.ModuleOutPath); ok && !stub {
-			// The path contains an embedded OutputPath so reuse that.
-			outputFile = moduleOutPath.OutputPath
-		} else if outputPath, ok := jars[0].(android.OutputPath); ok && !stub {
-			// The path is an OutputPath so reuse it directly.
-			outputFile = outputPath
-		} else {
-			// The file is not in the out directory so create an OutputPath into which it can be copied
-			// and which the following code can use to refer to it.
+		if stub {
 			combinedJar := android.PathForModuleOut(ctx, "combined", jarName)
 			ctx.Build(pctx, android.BuildParams{
 				Rule:   android.Cp,
 				Input:  jars[0],
 				Output: combinedJar,
 			})
-			outputFile = combinedJar.OutputPath
+			outputFile = combinedJar
+		} else {
+			outputFile = jars[0]
 		}
 	} else {
 		combinedJar := android.PathForModuleOut(ctx, "combined", jarName)
 		TransformJarsToJar(ctx, combinedJar, "for javac", jars, manifest,
 			false, nil, nil)
-		outputFile = combinedJar.OutputPath
+		outputFile = combinedJar
 	}
 
 	// jarjar implementation jar if necessary
-	if j.expandJarjarRules != nil {
-		// Transform classes.jar into classes-jarjar.jar
-		jarjarFile := android.PathForModuleOut(ctx, "jarjar", jarName).OutputPath
-		TransformJarJar(ctx, jarjarFile, outputFile, j.expandJarjarRules)
-		outputFile = jarjarFile
+	jarjarFile := j.jarjarIfNecessary(ctx, outputFile, jarName, "")
+	outputFile = jarjarFile
 
-		// jarjar resource jar if necessary
-		if j.resourceJar != nil {
-			resourceJarJarFile := android.PathForModuleOut(ctx, "res-jarjar", jarName)
-			TransformJarJar(ctx, resourceJarJarFile, j.resourceJar, j.expandJarjarRules)
-			j.resourceJar = resourceJarJarFile
-		}
+	// jarjar resource jar if necessary
+	if j.resourceJar != nil {
+		resourceJarJarFile := j.jarjarIfNecessary(ctx, j.resourceJar, jarName, "resource")
+		j.resourceJar = resourceJarJarFile
+	}
 
-		if ctx.Failed() {
-			return
-		}
+	if ctx.Failed() {
+		return
 	}
 
 	// Check package restrictions if necessary.
@@ -1583,15 +1574,16 @@
 		// will check that the jar only contains the permitted packages. The new location will become
 		// the output file of this module.
 		inputFile := outputFile
-		outputFile = android.PathForModuleOut(ctx, "package-check", jarName).OutputPath
+		packageCheckOutputFile := android.PathForModuleOut(ctx, "package-check", jarName)
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Cp,
 			Input:  inputFile,
-			Output: outputFile,
+			Output: packageCheckOutputFile,
 			// Make sure that any dependency on the output file will cause ninja to run the package check
 			// rule.
 			Validation: pkgckFile,
 		})
+		outputFile = packageCheckOutputFile
 
 		// Check packages and create a timestamp file when complete.
 		CheckJarPackages(ctx, pkgckFile, outputFile, j.properties.Permitted_packages)
@@ -1626,7 +1618,7 @@
 	implementationAndResourcesJar := outputFile
 	if j.resourceJar != nil {
 		jars := android.Paths{j.resourceJar, implementationAndResourcesJar}
-		combinedJar := android.PathForModuleOut(ctx, "withres", jarName).OutputPath
+		combinedJar := android.PathForModuleOut(ctx, "withres", jarName)
 		TransformJarsToJar(ctx, combinedJar, "for resources", jars, manifest,
 			false, nil, nil)
 		implementationAndResourcesJar = combinedJar
@@ -1653,7 +1645,7 @@
 					android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags"))
 			}
 			// Dex compilation
-			var dexOutputFile android.OutputPath
+			var dexOutputFile android.Path
 			params := &compileDexParams{
 				flags:         flags,
 				sdkVersion:    j.SdkVersion(ctx),
@@ -1680,17 +1672,17 @@
 
 			// If r8/d8 provides a profile that matches the optimized dex, use that for dexpreopt.
 			if dexArtProfileOutput != nil {
-				j.dexpreopter.SetRewrittenProfile(*dexArtProfileOutput)
+				j.dexpreopter.SetRewrittenProfile(dexArtProfileOutput)
 			}
 
 			// merge dex jar with resources if necessary
 			if j.resourceJar != nil {
 				jars := android.Paths{dexOutputFile, j.resourceJar}
-				combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName).OutputPath
+				combinedJar := android.PathForModuleOut(ctx, "dex-withres", jarName)
 				TransformJarsToJar(ctx, combinedJar, "for dex resources", jars, android.OptionalPath{},
 					false, nil, nil)
 				if *j.dexProperties.Uncompress_dex {
-					combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName).OutputPath
+					combinedAlignedJar := android.PathForModuleOut(ctx, "dex-withres-aligned", jarName)
 					TransformZipAlign(ctx, combinedAlignedJar, combinedJar, nil)
 					dexOutputFile = combinedAlignedJar
 				} else {
@@ -1760,7 +1752,7 @@
 
 	ctx.CheckbuildFile(outputFile)
 
-	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 		HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
 		RepackagedHeaderJars:                android.PathsIfNonNil(j.repackagedHeaderJarFile),
 		TransitiveLibsHeaderJars:            j.transitiveLibsHeaderJars,
@@ -1850,7 +1842,7 @@
 }
 
 func (j *Module) compileJavaClasses(ctx android.ModuleContext, jarName string, idx int,
-	srcFiles, srcJars android.Paths, flags javaBuilderFlags, extraJarDeps android.Paths) android.WritablePath {
+	srcFiles, srcJars android.Paths, flags javaBuilderFlags, extraJarDeps android.Paths) android.Path {
 
 	kzipName := pathtools.ReplaceExtension(jarName, "kzip")
 	annoSrcJar := android.PathForModuleOut(ctx, "javac", "anno.srcjar")
@@ -1860,7 +1852,7 @@
 		jarName += strconv.Itoa(idx)
 	}
 
-	classes := android.PathForModuleOut(ctx, "javac", jarName).OutputPath
+	classes := android.PathForModuleOut(ctx, "javac", jarName)
 	TransformJavaToClasses(ctx, classes, idx, srcFiles, srcJars, annoSrcJar, flags, extraJarDeps)
 
 	if ctx.Config().EmitXrefRules() && ctx.Module() == ctx.PrimaryModule() {
@@ -1903,16 +1895,13 @@
 
 func (j *Module) compileJavaHeader(ctx android.ModuleContext, srcFiles, srcJars android.Paths,
 	deps deps, flags javaBuilderFlags, jarName string,
-	extraJars android.Paths) (headerJar, jarjarAndDepsHeaderJar, jarjarAndDepsRepackagedHeaderJar android.Path) {
+	extraJars android.Paths) (headerJar android.Path, combinedHeaderJar android.Path) {
 
 	var jars android.Paths
 	if len(srcFiles) > 0 || len(srcJars) > 0 {
 		// Compile java sources into turbine.jar.
 		turbineJar := android.PathForModuleOut(ctx, "turbine", jarName)
 		TransformJavaToHeaderClasses(ctx, turbineJar, srcFiles, srcJars, flags)
-		if ctx.Failed() {
-			return nil, nil, nil
-		}
 		jars = append(jars, turbineJar)
 		headerJar = turbineJar
 	}
@@ -1925,40 +1914,18 @@
 
 	// we cannot skip the combine step for now if there is only one jar
 	// since we have to strip META-INF/TRANSITIVE dir from turbine.jar
-	combinedJar := android.PathForModuleOut(ctx, "turbine-combined", jarName)
-	TransformJarsToJar(ctx, combinedJar, "for turbine", jars, android.OptionalPath{},
+	combinedHeaderJarOutputPath := android.PathForModuleOut(ctx, "turbine-combined", jarName)
+	TransformJarsToJar(ctx, combinedHeaderJarOutputPath, "for turbine", jars, android.OptionalPath{},
 		false, nil, []string{"META-INF/TRANSITIVE"})
-	jarjarAndDepsHeaderJar = combinedJar
 
-	if j.expandJarjarRules != nil {
-		// Transform classes.jar into classes-jarjar.jar
-		jarjarFile := android.PathForModuleOut(ctx, "turbine-jarjar", jarName)
-		TransformJarJar(ctx, jarjarFile, jarjarAndDepsHeaderJar, j.expandJarjarRules)
-		jarjarAndDepsHeaderJar = jarjarFile
-		if ctx.Failed() {
-			return nil, nil, nil
-		}
-	}
-
-	if j.repackageJarjarRules != nil {
-		repackagedJarjarFile := android.PathForModuleOut(ctx, "repackaged-turbine-jarjar", jarName)
-		TransformJarJar(ctx, repackagedJarjarFile, jarjarAndDepsHeaderJar, j.repackageJarjarRules)
-		jarjarAndDepsRepackagedHeaderJar = repackagedJarjarFile
-		if ctx.Failed() {
-			return nil, nil, nil
-		}
-	} else {
-		jarjarAndDepsRepackagedHeaderJar = jarjarAndDepsHeaderJar
-	}
-
-	return headerJar, jarjarAndDepsHeaderJar, jarjarAndDepsRepackagedHeaderJar
+	return headerJar, combinedHeaderJarOutputPath
 }
 
 func (j *Module) instrument(ctx android.ModuleContext, flags javaBuilderFlags,
-	classesJar android.Path, jarName string, specs string) android.OutputPath {
+	classesJar android.Path, jarName string, specs string) android.Path {
 
 	jacocoReportClassesFile := android.PathForModuleOut(ctx, "jacoco-report-classes", jarName)
-	instrumentedJar := android.PathForModuleOut(ctx, "jacoco", jarName).OutputPath
+	instrumentedJar := android.PathForModuleOut(ctx, "jacoco", jarName)
 
 	jacocoInstrumentJar(ctx, instrumentedJar, jacocoReportClassesFile, classesJar, specs)
 
@@ -1993,23 +1960,24 @@
 			return
 		}
 
-		dep, _ := android.OtherModuleProvider(ctx, module, JavaInfoProvider)
-		tag := ctx.OtherModuleDependencyTag(module)
-		_, isUsesLibDep := tag.(usesLibraryDependencyTag)
-		if tag == libTag || tag == r8LibraryJarTag || isUsesLibDep {
-			directLibs = append(directLibs, dep.HeaderJars...)
-		} else if tag == staticLibTag {
-			directStaticLibs = append(directStaticLibs, dep.HeaderJars...)
-		} else {
-			// Don't propagate transitive libs for other kinds of dependencies.
-			return
-		}
+		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+			tag := ctx.OtherModuleDependencyTag(module)
+			_, isUsesLibDep := tag.(usesLibraryDependencyTag)
+			if tag == libTag || tag == r8LibraryJarTag || isUsesLibDep {
+				directLibs = append(directLibs, dep.HeaderJars...)
+			} else if tag == staticLibTag {
+				directStaticLibs = append(directStaticLibs, dep.HeaderJars...)
+			} else {
+				// Don't propagate transitive libs for other kinds of dependencies.
+				return
+			}
 
-		if dep.TransitiveLibsHeaderJars != nil {
-			transitiveLibs = append(transitiveLibs, dep.TransitiveLibsHeaderJars)
-		}
-		if dep.TransitiveStaticLibsHeaderJars != nil {
-			transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJars)
+			if dep.TransitiveLibsHeaderJars != nil {
+				transitiveLibs = append(transitiveLibs, dep.TransitiveLibsHeaderJars)
+			}
+			if dep.TransitiveStaticLibsHeaderJars != nil {
+				transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJars)
+			}
 		}
 	})
 	j.transitiveLibsHeaderJars = android.NewDepSet(android.POSTORDER, directLibs, transitiveLibs)
@@ -2115,9 +2083,10 @@
 	ctx.VisitDirectDeps(func(module android.Module) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if tag == staticLibTag {
-			depInfo, _ := android.OtherModuleProvider(ctx, module, JavaInfoProvider)
-			if depInfo.TransitiveSrcFiles != nil {
-				fromDeps = append(fromDeps, depInfo.TransitiveSrcFiles)
+			if depInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+				if depInfo.TransitiveSrcFiles != nil {
+					fromDeps = append(fromDeps, depInfo.TransitiveSrcFiles)
+				}
 			}
 		}
 	})
@@ -2733,15 +2702,25 @@
 }
 
 // Repackage the flags if the jarjar rule txt for the flags is generated
-func (j *Module) repackageFlagsIfNecessary(ctx android.ModuleContext, infile android.WritablePath, jarName, info string) android.WritablePath {
+func (j *Module) repackageFlagsIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) android.Path {
 	if j.repackageJarjarRules == nil {
 		return infile
 	}
-	repackagedJarjarFile := android.PathForModuleOut(ctx, "repackaged-jarjar", info+jarName)
+	repackagedJarjarFile := android.PathForModuleOut(ctx, "repackaged-jarjar", info, jarName)
 	TransformJarJar(ctx, repackagedJarjarFile, infile, j.repackageJarjarRules)
 	return repackagedJarjarFile
 }
 
+func (j *Module) jarjarIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) android.Path {
+	if j.expandJarjarRules == nil {
+		return infile
+	}
+	jarjarFile := android.PathForModuleOut(ctx, "jarjar", info, jarName)
+	TransformJarJar(ctx, jarjarFile, infile, j.expandJarjarRules)
+	return jarjarFile
+
+}
+
 func addPlugins(deps *deps, pluginJars android.Paths, pluginClasses ...string) {
 	deps.processorPath = append(deps.processorPath, pluginJars...)
 	deps.processorClasses = append(deps.processorClasses, pluginClasses...)
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index ab72e8b..cee7a19 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -41,7 +41,7 @@
 }
 
 java_library {
-    name: "core.current.stubs.from-source",
+    name: "core.current.stubs",
     defaults: [
         "core.current.stubs.defaults",
     ],
@@ -52,8 +52,12 @@
     ],
 }
 
+// Used for bootstrapping ART system modules
 java_api_library {
     name: "core.current.stubs.from-text",
+    defaults: [
+        "core.current.stubs.defaults",
+    ],
     api_surface: "core",
     api_contributions: [
         "art.module.public.api.stubs.source.api.contribution",
@@ -68,27 +72,7 @@
 }
 
 java_library {
-    name: "core.current.stubs",
-    defaults: [
-        "core.current.stubs.defaults",
-    ],
-    static_libs: [
-        "core.current.stubs.from-source",
-    ],
-    product_variables: {
-        build_from_text_stub: {
-            static_libs: [
-                "core.current.stubs.from-text",
-            ],
-            exclude_static_libs: [
-                "core.current.stubs.from-source",
-            ],
-        },
-    },
-}
-
-java_library {
-    name: "core.current.stubs.exportable.from-source",
+    name: "core.current.stubs.exportable",
     defaults: [
         "core.current.stubs.defaults",
     ],
@@ -103,16 +87,6 @@
     },
 }
 
-java_library {
-    name: "core.current.stubs.exportable",
-    defaults: [
-        "core.current.stubs.defaults",
-    ],
-    static_libs: [
-        "core.current.stubs.exportable.from-source",
-    ],
-}
-
 // Distributed with the SDK for turning into system modules to compile apps
 // against.
 //
@@ -201,26 +175,6 @@
         "core.module_lib.stubs.defaults",
     ],
     static_libs: [
-        "core.module_lib.stubs.from-source",
-    ],
-    product_variables: {
-        build_from_text_stub: {
-            static_libs: [
-                "core.module_lib.stubs.from-text",
-            ],
-            exclude_static_libs: [
-                "core.module_lib.stubs.from-source",
-            ],
-        },
-    },
-}
-
-java_library {
-    name: "core.module_lib.stubs.from-source",
-    defaults: [
-        "core.module_lib.stubs.defaults",
-    ],
-    static_libs: [
         "art.module.public.api.stubs.module_lib",
 
         // Replace the following with the module-lib correspondence when Conscrypt or i18N module
@@ -231,27 +185,6 @@
     ],
 }
 
-java_api_library {
-    name: "core.module_lib.stubs.from-text",
-    api_surface: "module-lib",
-    api_contributions: [
-        "art.module.public.api.stubs.source.api.contribution",
-        "art.module.public.api.stubs.source.system.api.contribution",
-        "art.module.public.api.stubs.source.module_lib.api.contribution",
-
-        // Add the module-lib correspondence when Conscrypt or i18N module
-        // provides @SystemApi(MODULE_LIBRARIES). Currently, assume that only ART module provides
-        // @SystemApi(MODULE_LIBRARIES).
-        "conscrypt.module.public.api.stubs.source.api.contribution",
-        "i18n.module.public.api.stubs.source.api.contribution",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    visibility: ["//visibility:private"],
-    stubs_type: "everything",
-}
-
 // Produces a dist file that is used by the
 // prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
 // directory.
@@ -311,7 +244,7 @@
 // API annotations are available to the dex tools that enable enforcement of runtime
 // accessibility. b/119068555
 java_library {
-    name: "legacy.core.platform.api.stubs.from-source",
+    name: "legacy.core.platform.api.stubs",
     visibility: core_platform_visibility,
     defaults: [
         "core.platform.api.stubs.defaults",
@@ -324,7 +257,7 @@
 }
 
 java_library {
-    name: "legacy.core.platform.api.stubs.exportable.from-source",
+    name: "legacy.core.platform.api.stubs.exportable",
     visibility: core_platform_visibility,
     defaults: [
         "core.platform.api.stubs.defaults",
@@ -348,53 +281,6 @@
     ],
 }
 
-java_api_library {
-    name: "legacy.core.platform.api.stubs.from-text",
-    api_surface: "core_platform",
-    defaults: [
-        "android_core_platform_stubs_current_contributions",
-    ],
-    api_contributions: [
-        "legacy.i18n.module.platform.api.stubs.source.api.contribution",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    stubs_type: "everything",
-}
-
-java_library {
-    name: "legacy.core.platform.api.stubs",
-    visibility: core_platform_visibility,
-    defaults: [
-        "core.platform.api.stubs.defaults",
-    ],
-    static_libs: [
-        "legacy.core.platform.api.stubs.from-source",
-    ],
-    product_variables: {
-        build_from_text_stub: {
-            static_libs: [
-                "legacy.core.platform.api.stubs.from-text",
-            ],
-            exclude_static_libs: [
-                "legacy.core.platform.api.stubs.from-source",
-            ],
-        },
-    },
-}
-
-java_library {
-    name: "legacy.core.platform.api.stubs.exportable",
-    visibility: core_platform_visibility,
-    defaults: [
-        "core.platform.api.stubs.defaults",
-    ],
-    static_libs: [
-        "legacy.core.platform.api.stubs.exportable.from-source",
-    ],
-}
-
 java_defaults {
     name: "core.platform.api.stubs.defaults",
     hostdex: true,
@@ -424,7 +310,7 @@
 }
 
 java_library {
-    name: "stable.core.platform.api.stubs.from-source",
+    name: "stable.core.platform.api.stubs",
     visibility: core_platform_visibility,
     defaults: [
         "core.platform.api.stubs.defaults",
@@ -437,42 +323,6 @@
     ],
 }
 
-java_api_library {
-    name: "stable.core.platform.api.stubs.from-text",
-    api_surface: "core_platform",
-    defaults: [
-        "android_core_platform_stubs_current_contributions",
-    ],
-    api_contributions: [
-        "stable.i18n.module.platform.api.stubs.source.api.contribution",
-    ],
-    libs: [
-        "stub-annotations",
-    ],
-    stubs_type: "everything",
-}
-
-java_library {
-    name: "stable.core.platform.api.stubs",
-    visibility: core_platform_visibility,
-    defaults: [
-        "core.platform.api.stubs.defaults",
-    ],
-    static_libs: [
-        "stable.core.platform.api.stubs.from-source",
-    ],
-    product_variables: {
-        build_from_text_stub: {
-            static_libs: [
-                "stable.core.platform.api.stubs.from-text",
-            ],
-            exclude_static_libs: [
-                "stable.core.platform.api.stubs.from-source",
-            ],
-        },
-    },
-}
-
 // Same as stable.core.platform.api.stubs, but android annotations are
 // stripped. This is used by the Java toolchain, while the annotated stub is to
 // be used by Kotlin one.
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 3f8735c..90aeb4e 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -130,7 +130,7 @@
 		d.combinedHeaderJar = d.headerJars[0]
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 		HeaderJars:                     d.headerJars,
 		ImplementationAndResourcesJars: d.implementationAndResourceJars,
 		ImplementationJars:             d.implementationJars,
diff --git a/java/dex.go b/java/dex.go
index 7bb6925..d88e8f8 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -291,8 +291,9 @@
 	// See b/20667396
 	var proguardRaiseDeps classpath
 	ctx.VisitDirectDepsWithTag(proguardRaiseTag, func(m android.Module) {
-		dep, _ := android.OtherModuleProvider(ctx, m, JavaInfoProvider)
-		proguardRaiseDeps = append(proguardRaiseDeps, dep.RepackagedHeaderJars...)
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			proguardRaiseDeps = append(proguardRaiseDeps, dep.RepackagedHeaderJars...)
+		}
 	})
 
 	r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars"))
@@ -428,7 +429,7 @@
 }
 
 // Return the compiled dex jar and (optional) profile _after_ r8 optimization
-func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) (android.OutputPath, *android.OutputPath) {
+func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) (android.Path, android.Path) {
 
 	// Compile classes.jar into classes.dex and then javalib.jar
 	javalibJar := android.PathForModuleOut(ctx, "dex", dexParams.jarName).OutputPath
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 7949244..a38642a 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -359,7 +359,7 @@
 	d.dexpreopt(ctx, libraryName, dexJarFile)
 }
 
-func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, libName string, dexJarFile android.WritablePath) {
+func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, libName string, dexJarFile android.Path) {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
 	// TODO(b/148690468): The check on d.installPath is to bail out in cases where
@@ -616,10 +616,8 @@
 	return installPath, relDir, installBase
 }
 
-// RuleBuilder.Install() adds output-to-install copy pairs to a list for Make. To share this
-// information with PackagingSpec in soong, call PackageFile for them.
-// The install path and the target install partition of the module must be the same.
-func packageFile(ctx android.ModuleContext, install android.RuleBuilderInstall) {
+// installFile will install the file if `install` path and the target install partition are the same.
+func installFile(ctx android.ModuleContext, install android.RuleBuilderInstall) {
 	installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
 	// Empty name means the install partition is not for the target image.
 	// For the system image, files for "apex" and "system_other" are skipped here.
@@ -628,7 +626,7 @@
 	// TODO(b/320196894): Files for "system_other" are skipped because soong creates the system
 	// image only for now.
 	if name != "" {
-		ctx.PackageFile(installPath.Join(ctx, relDir), name, install.From)
+		ctx.InstallFile(installPath.Join(ctx, relDir), name, install.From)
 	}
 }
 
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index defa82c..56e007b 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -491,6 +491,11 @@
 	// Build path to a config file that Soong writes for Make (to be used in makefiles that install
 	// the default boot image).
 	dexpreoptConfigForMake android.WritablePath
+
+	// Build path to the boot framework profile.
+	// This is used as the `OutputFile` in `AndroidMkEntries`.
+	// A non-nil value ensures that this singleton module does not get skipped in AndroidMkEntries processing.
+	bootFrameworkProfile android.WritablePath
 }
 
 func (dbj *dexpreoptBootJars) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -603,7 +608,8 @@
 		installs := generateBootImage(ctx, config)
 		profileInstalls = append(profileInstalls, installs...)
 		if config == d.defaultBootImage {
-			_, installs := bootFrameworkProfileRule(ctx, config)
+			bootProfile, installs := bootFrameworkProfileRule(ctx, config)
+			d.bootFrameworkProfile = bootProfile
 			profileInstalls = append(profileInstalls, installs...)
 		}
 	}
@@ -613,7 +619,7 @@
 			profileLicenseMetadataFile: android.OptionalPathForPath(ctx.LicenseMetadataFile()),
 		})
 		for _, install := range profileInstalls {
-			packageFile(ctx, install)
+			installFile(ctx, install)
 		}
 	}
 }
@@ -939,24 +945,21 @@
 	}
 
 	for _, install := range image.installs {
-		packageFile(ctx, install)
+		installFile(ctx, install)
 	}
 
 	for _, install := range image.vdexInstalls {
-		if image.target.Arch.ArchType.Name != ctx.DeviceConfig().DeviceArch() {
-			// Note that the vdex files are identical between architectures. If the target image is
-			// not for the primary architecture create symlinks to share the vdex of the primary
-			// architecture with the other architectures.
-			//
-			// Assuming that the install path has the architecture name with it, replace the
-			// architecture name with the primary architecture name to find the source vdex file.
-			installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
-			if name != "" {
-				srcRelDir := strings.Replace(relDir, image.target.Arch.ArchType.Name, ctx.DeviceConfig().DeviceArch(), 1)
-				ctx.InstallSymlink(installPath.Join(ctx, relDir), name, installPath.Join(ctx, srcRelDir, name))
-			}
-		} else {
-			packageFile(ctx, install)
+		installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
+		if name == "" {
+			continue
+		}
+		// Note that the vdex files are identical between architectures. Copy the vdex to a no arch directory
+		// and create symlinks for both the primary and secondary arches.
+		ctx.InstallSymlink(installPath.Join(ctx, relDir), name, installPath.Join(ctx, "framework", name))
+		if image.target.Arch.ArchType.Name == ctx.DeviceConfig().DeviceArch() {
+			// Copy the vdex from the primary arch to the no-arch directory
+			// e.g. /system/framework/$bootjar.vdex
+			ctx.InstallFile(installPath.Join(ctx, "framework"), name, install.From)
 		}
 	}
 }
@@ -1234,7 +1237,7 @@
 
 	profile := bootImageProfileRuleCommon(ctx, image.name, image.dexPathsDeps.Paths(), image.getAnyAndroidVariant().dexLocationsDeps)
 
-	if image == defaultBootImageConfig(ctx) {
+	if image == defaultBootImageConfig(ctx) && profile != nil {
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Install(profile, "/system/etc/boot-image.prof")
 		return profile, rule.Installs()
@@ -1380,3 +1383,12 @@
 		ctx.Strict("DEXPREOPT_IMAGE_NAMES", strings.Join(getImageNames(), " "))
 	}
 }
+
+// Add one of the outputs in `OutputFile`
+// This ensures that this singleton module does not get skipped when writing out/soong/Android-*.mk
+func (d *dexpreoptBootJars) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(d.bootFrameworkProfile),
+	}}
+}
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 5441a3b..b1a9deb 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -98,8 +98,9 @@
 	// processing.
 	classesJars := android.Paths{classesJar}
 	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
-		javaInfo, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
-		classesJars = append(classesJars, javaInfo.ImplementationJars...)
+		if javaInfo, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
+			classesJars = append(classesJars, javaInfo.ImplementationJars...)
+		}
 	})
 	h.classesJarPaths = classesJars
 
@@ -151,7 +152,7 @@
 //
 // Otherwise, it creates a copy of the supplied dex file into which it has encoded the hiddenapi
 // flags and returns this instead of the supplied dex jar.
-func (h *hiddenAPI) hiddenAPIEncodeDex(ctx android.ModuleContext, dexJar android.OutputPath) android.OutputPath {
+func (h *hiddenAPI) hiddenAPIEncodeDex(ctx android.ModuleContext, dexJar android.Path) android.Path {
 
 	if !h.active {
 		return dexJar
diff --git a/java/java.go b/java/java.go
index b320732..be3f90b 100644
--- a/java/java.go
+++ b/java/java.go
@@ -315,14 +315,14 @@
 	AconfigIntermediateCacheOutputPaths android.Paths
 }
 
-var JavaInfoProvider = blueprint.NewProvider[JavaInfo]()
+var JavaInfoProvider = blueprint.NewProvider[*JavaInfo]()
 
 // SyspropPublicStubInfo contains info about the sysprop public stub library that corresponds to
 // the sysprop implementation library.
 type SyspropPublicStubInfo struct {
 	// JavaInfo is the JavaInfoProvider of the sysprop public stub library that corresponds to
 	// the sysprop implementation library.
-	JavaInfo JavaInfo
+	JavaInfo *JavaInfo
 }
 
 var SyspropPublicStubInfoProvider = blueprint.NewProvider[SyspropPublicStubInfo]()
@@ -432,7 +432,6 @@
 	r8LibraryJarTag         = dependencyTag{name: "r8-libraryjar", runtimeLinked: true}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	javaApiContributionTag  = dependencyTag{name: "java-api-contribution"}
-	depApiSrcsTag           = dependencyTag{name: "dep-api-srcs"}
 	aconfigDeclarationTag   = dependencyTag{name: "aconfig-declaration"}
 	jniInstallTag           = dependencyTag{name: "jni install", runtimeLinked: true, installable: true}
 	binaryInstallTag        = dependencyTag{name: "binary install", runtimeLinked: true, installable: true}
@@ -2005,12 +2004,6 @@
 	// merge zipped after metalava invocation
 	Static_libs []string
 
-	// Java Api library to provide the full API surface stub jar file.
-	// If this property is set, the stub jar of this module is created by
-	// extracting the compiled class files provided by the
-	// full_api_surface_stub module.
-	Full_api_surface_stub *string
-
 	// Version of previously released API file for compatibility check.
 	Previous_api *string `android:"path"`
 
@@ -2043,6 +2036,15 @@
 	// List of hard coded filegroups containing Metalava config files that are passed to every
 	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
 	ConfigFiles []string `android:"path" blueprint:"mutated"`
+
+	// If not blank, set to the version of the sdk to compile against.
+	// Defaults to an empty string, which compiles the module against the private platform APIs.
+	// Values are of one of the following forms:
+	// 1) numerical API level, "current", "none", or "core_platform"
+	// 2) An SDK kind with an API level: "<sdk kind>_<API level>"
+	// See build/soong/android/sdk_version.go for the complete and up to date list of SDK kinds.
+	// If the SDK kind is empty, it will be set to public.
+	Sdk_version *string
 }
 
 func ApiLibraryFactory() android.Module {
@@ -2141,40 +2143,6 @@
 	}
 }
 
-// This method extracts the stub class files from the stub jar file provided
-// from full_api_surface_stub module instead of compiling the srcjar generated from invoking metalava.
-// This method is used because metalava can generate compilable from-text stubs only when
-// the codebase encompasses all classes listed in the input API text file, and a class can extend
-// a class that is not within the same API domain.
-func (al *ApiLibrary) extractApiSrcs(ctx android.ModuleContext, rule *android.RuleBuilder, stubsDir android.OptionalPath, fullApiSurfaceStubJar android.Path) {
-	classFilesList := android.PathForModuleOut(ctx, "metalava", "classes.txt")
-	unzippedSrcJarDir := android.PathForModuleOut(ctx, "metalava", "unzipDir")
-
-	rule.Command().
-		BuiltTool("list_files").
-		Text(stubsDir.String()).
-		FlagWithOutput("--out ", classFilesList).
-		FlagWithArg("--extensions ", ".java").
-		FlagWithArg("--root ", unzippedSrcJarDir.String()).
-		Flag("--classes")
-
-	rule.Command().
-		Text("unzip").
-		Flag("-q").
-		Input(fullApiSurfaceStubJar).
-		FlagWithArg("-d ", unzippedSrcJarDir.String())
-
-	rule.Command().
-		BuiltTool("soong_zip").
-		Flag("-jar").
-		Flag("-write_if_changed").
-		Flag("-ignore_missing_files").
-		Flag("-quiet").
-		FlagWithArg("-C ", unzippedSrcJarDir.String()).
-		FlagWithInput("-l ", classFilesList).
-		FlagWithOutput("-o ", al.stubsJarWithoutStaticLibs)
-}
-
 func (al *ApiLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
 	apiContributions := al.properties.Api_contributions
 	addValidations := !ctx.Config().IsEnvTrue("DISABLE_STUB_VALIDATION") &&
@@ -2201,14 +2169,18 @@
 			}
 		}
 	}
+	if ctx.Device() {
+		sdkDep := decodeSdkDep(ctx, android.SdkContext(al))
+		if sdkDep.useModule {
+			ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
+			ctx.AddVariationDependencies(nil, libTag, sdkDep.classpath...)
+			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
+
+		}
+	}
 	ctx.AddVariationDependencies(nil, libTag, al.properties.Libs...)
 	ctx.AddVariationDependencies(nil, staticLibTag, al.properties.Static_libs...)
-	if al.properties.Full_api_surface_stub != nil {
-		ctx.AddVariationDependencies(nil, depApiSrcsTag, String(al.properties.Full_api_surface_stub))
-	}
-	if al.properties.System_modules != nil {
-		ctx.AddVariationDependencies(nil, systemModulesTag, String(al.properties.System_modules))
-	}
+
 	for _, aconfigDeclarationsName := range al.properties.Aconfig_declarations {
 		ctx.AddDependency(ctx.Module(), aconfigDeclarationTag, aconfigDeclarationsName)
 	}
@@ -2264,8 +2236,8 @@
 
 	var srcFilesInfo []JavaApiImportInfo
 	var classPaths android.Paths
+	var bootclassPaths android.Paths
 	var staticLibs android.Paths
-	var depApiSrcsStubsJar android.Path
 	var systemModulesPaths android.Paths
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		tag := ctx.OtherModuleDependencyTag(dep)
@@ -2277,14 +2249,17 @@
 			}
 			srcFilesInfo = append(srcFilesInfo, provider)
 		case libTag:
-			provider, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
-			classPaths = append(classPaths, provider.HeaderJars...)
+			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
+				classPaths = append(classPaths, provider.HeaderJars...)
+			}
+		case bootClasspathTag:
+			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
+				bootclassPaths = append(bootclassPaths, provider.HeaderJars...)
+			}
 		case staticLibTag:
-			provider, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
-			staticLibs = append(staticLibs, provider.HeaderJars...)
-		case depApiSrcsTag:
-			provider, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
-			depApiSrcsStubsJar = provider.HeaderJars[0]
+			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
+				staticLibs = append(staticLibs, provider.HeaderJars...)
+			}
 		case systemModulesTag:
 			module := dep.(SystemModulesProvider)
 			systemModulesPaths = append(systemModulesPaths, module.HeaderJars()...)
@@ -2319,7 +2294,10 @@
 
 	configFiles := android.PathsForModuleSrc(ctx, al.properties.ConfigFiles)
 
-	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, systemModulesPaths, configFiles)
+	combinedPaths := append(([]android.Path)(nil), systemModulesPaths...)
+	combinedPaths = append(combinedPaths, classPaths...)
+	combinedPaths = append(combinedPaths, bootclassPaths...)
+	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, combinedPaths, configFiles)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
@@ -2337,9 +2315,6 @@
 	al.stubsJarWithoutStaticLibs = android.PathForModuleOut(ctx, "metalava", "stubs.jar")
 	al.stubsJar = android.PathForModuleOut(ctx, ctx.ModuleName(), fmt.Sprintf("%s.jar", ctx.ModuleName()))
 
-	if depApiSrcsStubsJar != nil {
-		al.extractApiSrcs(ctx, rule, stubsDir, depApiSrcsStubsJar)
-	}
 	rule.Command().
 		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
@@ -2350,19 +2325,18 @@
 
 	rule.Build("metalava", "metalava merged text")
 
-	if depApiSrcsStubsJar == nil {
-		var flags javaBuilderFlags
-		flags.javaVersion = getStubsJavaVersion()
-		flags.javacFlags = strings.Join(al.properties.Javacflags, " ")
-		flags.classpath = classpath(classPaths)
-		flags.bootClasspath = classpath(systemModulesPaths)
-
-		annoSrcJar := android.PathForModuleOut(ctx, ctx.ModuleName(), "anno.srcjar")
-
-		TransformJavaToClasses(ctx, al.stubsJarWithoutStaticLibs, 0, android.Paths{},
-			android.Paths{al.stubsSrcJar}, annoSrcJar, flags, android.Paths{})
+	javacFlags := javaBuilderFlags{
+		javaVersion:   getStubsJavaVersion(),
+		javacFlags:    strings.Join(al.properties.Javacflags, " "),
+		classpath:     classpath(classPaths),
+		bootClasspath: classpath(append(systemModulesPaths, bootclassPaths...)),
 	}
 
+	annoSrcJar := android.PathForModuleOut(ctx, ctx.ModuleName(), "anno.srcjar")
+
+	TransformJavaToClasses(ctx, al.stubsJarWithoutStaticLibs, 0, android.Paths{},
+		android.Paths{al.stubsSrcJar}, annoSrcJar, javacFlags, android.Paths{})
+
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().
 		BuiltTool("merge_zips").
@@ -2373,7 +2347,7 @@
 
 	// compile stubs to .dex for hiddenapi processing
 	dexParams := &compileDexParams{
-		flags:         javaBuilderFlags{},
+		flags:         javacFlags,
 		sdkVersion:    al.SdkVersion(ctx),
 		minSdkVersion: al.MinSdkVersion(ctx),
 		classesJar:    al.stubsJar,
@@ -2387,7 +2361,7 @@
 
 	ctx.Phony(ctx.ModuleName(), al.stubsJar)
 
-	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(al.stubsJar),
 		ImplementationAndResourcesJars: android.PathsIfNonNil(al.stubsJar),
 		ImplementationJars:             android.PathsIfNonNil(al.stubsJar),
@@ -2409,14 +2383,28 @@
 	return nil
 }
 
-// java_api_library constitutes the sdk, and does not build against one
+// Most java_api_library constitues the sdk, but there are some java_api_library that
+// does not contribute to the api surface. Such modules are allowed to set sdk_version
+// other than "none"
 func (al *ApiLibrary) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
-	return android.SdkSpecNone
+	return android.SdkSpecFrom(ctx, proptools.String(al.properties.Sdk_version))
 }
 
 // java_api_library is always at "current". Return FutureApiLevel
 func (al *ApiLibrary) MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
-	return android.FutureApiLevel
+	return al.SdkVersion(ctx).ApiLevel
+}
+
+func (al *ApiLibrary) ReplaceMaxSdkVersionPlaceholder(ctx android.EarlyModuleContext) android.ApiLevel {
+	return al.SdkVersion(ctx).ApiLevel
+}
+
+func (al *ApiLibrary) SystemModules() string {
+	return proptools.String(al.properties.System_modules)
+}
+
+func (al *ApiLibrary) TargetSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
+	return al.SdkVersion(ctx).ApiLevel
 }
 
 func (al *ApiLibrary) IDEInfo(i *android.IdeInfo) {
@@ -2434,9 +2422,6 @@
 	if al.properties.System_modules != nil {
 		ret = append(ret, proptools.String(al.properties.System_modules))
 	}
-	if al.properties.Full_api_surface_stub != nil {
-		ret = append(ret, proptools.String(al.properties.Full_api_surface_stub))
-	}
 	// Other non java_library dependencies like java_api_contribution are ignored for now.
 	return ret
 }
@@ -2444,6 +2429,7 @@
 // implement the following interfaces for hiddenapi processing
 var _ hiddenAPIModule = (*ApiLibrary)(nil)
 var _ UsesLibraryDependency = (*ApiLibrary)(nil)
+var _ android.SdkContext = (*ApiLibrary)(nil)
 
 // implement the following interface for IDE completion.
 var _ android.IDEInfo = (*ApiLibrary)(nil)
@@ -2779,7 +2765,7 @@
 			setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 
-			var dexOutputFile android.OutputPath
+			var dexOutputFile android.Path
 			dexParams := &compileDexParams{
 				flags:         flags,
 				sdkVersion:    j.SdkVersion(ctx),
@@ -2804,7 +2790,7 @@
 		}
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
+	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(j.combinedHeaderFile),
 		TransitiveLibsHeaderJars:       j.transitiveLibsHeaderJars,
 		TransitiveStaticLibsHeaderJars: j.transitiveStaticLibsHeaderJars,
@@ -2924,7 +2910,7 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 
 func (j *Import) IDEInfo(dpInfo *android.IdeInfo) {
-	dpInfo.Jars = append(dpInfo.Jars, j.PrebuiltSrcs()...)
+	dpInfo.Jars = append(dpInfo.Jars, j.combinedHeaderFile.String())
 }
 
 func (j *Import) IDECustomizedModuleName() string {
diff --git a/java/java_test.go b/java/java_test.go
index 33079f3..2d4fca2 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1342,12 +1342,12 @@
 		}
 		`)
 
-	checkBootClasspathForSystemModule(t, ctx, "lib-with-source-system-modules", "/source-jar.jar")
+	checkBootClasspathForLibWithSystemModule(t, ctx, "lib-with-source-system-modules", "/source-jar.jar")
 
-	checkBootClasspathForSystemModule(t, ctx, "lib-with-prebuilt-system-modules", "/prebuilt-jar.jar")
+	checkBootClasspathForLibWithSystemModule(t, ctx, "lib-with-prebuilt-system-modules", "/prebuilt-jar.jar")
 }
 
-func checkBootClasspathForSystemModule(t *testing.T, ctx *android.TestContext, moduleName string, expectedSuffix string) {
+func checkBootClasspathForLibWithSystemModule(t *testing.T, ctx *android.TestContext, moduleName string, expectedSuffix string) {
 	javacRule := ctx.ModuleForTests(moduleName, "android_common").Rule("javac")
 	bootClasspath := javacRule.Args["bootClasspath"]
 	if strings.HasPrefix(bootClasspath, "--system ") && strings.HasSuffix(bootClasspath, expectedSuffix) {
@@ -2256,61 +2256,6 @@
 	}
 }
 
-func TestJavaApiLibraryFullApiSurfaceStub(t *testing.T) {
-	provider_bp_a := `
-	java_api_contribution {
-		name: "foo1",
-		api_file: "current.txt",
-		api_surface: "public",
-	}
-	`
-	provider_bp_b := `
-	java_api_contribution {
-		name: "foo2",
-		api_file: "current.txt",
-		api_surface: "public",
-	}
-	`
-	lib_bp_a := `
-	java_api_library {
-		name: "lib1",
-		api_surface: "public",
-		api_contributions: ["foo1", "foo2"],
-		stubs_type: "everything",
-	}
-	`
-
-	ctx := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		android.FixtureMergeMockFs(
-			map[string][]byte{
-				"a/Android.bp": []byte(provider_bp_a),
-				"b/Android.bp": []byte(provider_bp_b),
-				"c/Android.bp": []byte(lib_bp_a),
-			},
-		),
-		android.FixtureMergeEnv(
-			map[string]string{
-				"DISABLE_STUB_VALIDATION": "true",
-			},
-		),
-	).RunTestWithBp(t, `
-		java_api_library {
-			name: "bar1",
-			api_surface: "public",
-			api_contributions: ["foo1"],
-			full_api_surface_stub: "lib1",
-			stubs_type: "everything",
-		}
-	`)
-
-	m := ctx.ModuleForTests("bar1", "android_common")
-	manifest := m.Output("metalava.sbox.textproto")
-	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx.TestContext, manifest)
-	manifestCommand := sboxProto.Commands[0].GetCommand()
-	android.AssertStringDoesContain(t, "Command expected to contain full_api_surface_stub output jar", manifestCommand, "lib1.jar")
-}
-
 func TestTransitiveSrcFiles(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
@@ -2511,9 +2456,6 @@
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 		android.FixtureMergeMockFs(
 			map[string][]byte{
 				"A.java": nil,
@@ -2534,12 +2476,8 @@
 			system_modules: "baz",
 		}
 	`)
-	m := result.ModuleForTests(apiScopePublic.apiLibraryModuleName("foo"), "android_common")
-	manifest := m.Output("metalava.sbox.textproto")
-	sboxProto := android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest)
-	manifestCommand := sboxProto.Commands[0].GetCommand()
-	classPathFlag := "--classpath __SBOX_SANDBOX_DIR__/out/soong/.intermediates/bar/android_common/turbine-combined/bar.jar"
-	android.AssertStringDoesContain(t, "command expected to contain classpath flag", manifestCommand, classPathFlag)
+
+	checkBootClasspathForLibWithSystemModule(t, result.TestContext, apiScopePublic.apiLibraryModuleName("foo"), "/bar.jar")
 }
 
 func TestApiLibraryDroidstubsDependency(t *testing.T) {
@@ -2547,9 +2485,6 @@
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 		android.FixtureMergeMockFs(
 			map[string][]byte{
 				"A.java": nil,
@@ -2598,7 +2533,6 @@
 		PrepareForTestWithJacocoInstrumentation,
 		FixtureWithLastReleaseApis("foo"),
 		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
 			config.SetBuildFromTextStub(true)
 		}),
 		android.FixtureModifyEnv(func(env map[string]string) {
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 38553a6..d794e51 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -168,9 +168,10 @@
 
 	var transitiveSrcFiles android.Paths
 	for _, module := range allModules {
-		depInfo, _ := android.OtherModuleProvider(ctx, module, JavaInfoProvider)
-		if depInfo.TransitiveSrcFiles != nil {
-			transitiveSrcFiles = append(transitiveSrcFiles, depInfo.TransitiveSrcFiles.ToList()...)
+		if depInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+			if depInfo.TransitiveSrcFiles != nil {
+				transitiveSrcFiles = append(transitiveSrcFiles, depInfo.TransitiveSrcFiles.ToList()...)
+			}
 		}
 	}
 	jarArgs := resourcePathsToJarArgs(transitiveSrcFiles)
diff --git a/java/robolectric.go b/java/robolectric.go
index 4cad5b1..26f4b71 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -214,12 +214,13 @@
 	}
 
 	handleLibDeps := func(dep android.Module, runtimeOnly bool) {
-		m, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
 		if !runtimeOnly {
 			r.libs = append(r.libs, ctx.OtherModuleName(dep))
 		}
 		if !android.InList(ctx.OtherModuleName(dep), config.FrameworkLibraries) {
-			combinedJarJars = append(combinedJarJars, m.ImplementationAndResourcesJars...)
+			if m, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
+				combinedJarJars = append(combinedJarJars, m.ImplementationAndResourcesJars...)
+			}
 		}
 	}
 
diff --git a/java/sdk_library.go b/java/sdk_library.go
index c5f7a1c..a8cc1b8 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -20,7 +20,6 @@
 	"path"
 	"path/filepath"
 	"reflect"
-	"regexp"
 	"sort"
 	"strings"
 	"sync"
@@ -427,22 +426,10 @@
 		apiScopeModuleLib,
 		apiScopeSystemServer,
 	}
-	apiLibraryAdditionalProperties = map[string]struct {
-		FullApiSurfaceStubLib     string
-		AdditionalApiContribution string
-	}{
-		"legacy.i18n.module.platform.api": {
-			FullApiSurfaceStubLib:     "legacy.core.platform.api.stubs",
-			AdditionalApiContribution: "i18n.module.public.api.stubs.source.api.contribution",
-		},
-		"stable.i18n.module.platform.api": {
-			FullApiSurfaceStubLib:     "stable.core.platform.api.stubs",
-			AdditionalApiContribution: "i18n.module.public.api.stubs.source.api.contribution",
-		},
-		"conscrypt.module.platform.api": {
-			FullApiSurfaceStubLib:     "stable.core.platform.api.stubs",
-			AdditionalApiContribution: "conscrypt.module.public.api.stubs.source.api.contribution",
-		},
+	apiLibraryAdditionalProperties = map[string]string{
+		"legacy.i18n.module.platform.api": "i18n.module.public.api.stubs.source.api.contribution",
+		"stable.i18n.module.platform.api": "i18n.module.public.api.stubs.source.api.contribution",
+		"conscrypt.module.platform.api":   "conscrypt.module.public.api.stubs.source.api.contribution",
 	}
 )
 
@@ -650,13 +637,6 @@
 		Legacy_errors_allowed *bool
 	}
 
-	// Determines if the module contributes to any api surfaces.
-	// This property should be set to true only if the module is listed under
-	// frameworks-base-api.bootclasspath in frameworks/base/api/Android.bp.
-	// Otherwise, this property should be set to false.
-	// Defaults to false.
-	Contribute_to_android_api *bool
-
 	// a list of aconfig_declarations module names that the stubs generated in this module
 	// depend on.
 	Aconfig_declarations []string
@@ -1065,28 +1045,6 @@
 	annotationsComponentName = "annotations.zip"
 )
 
-// A regular expression to match tags that reference a specific stubs component.
-//
-// It will only match if given a valid scope and a valid component. It is verfy strict
-// to ensure it does not accidentally match a similar looking tag that should be processed
-// by the embedded Library.
-var tagSplitter = func() *regexp.Regexp {
-	// Given a list of literal string items returns a regular expression that will
-	// match any one of the items.
-	choice := func(items ...string) string {
-		return `\Q` + strings.Join(items, `\E|\Q`) + `\E`
-	}
-
-	// Regular expression to match one of the scopes.
-	scopesRegexp := choice(allScopeNames...)
-
-	// Regular expression to match one of the components.
-	componentsRegexp := choice(stubsSourceComponentName, apiTxtComponentName, removedApiTxtComponentName, annotationsComponentName)
-
-	// Regular expression to match any combination of one scope and one component.
-	return regexp.MustCompile(fmt.Sprintf(`^\.(%s)\.(%s)$`, scopesRegexp, componentsRegexp))
-}()
-
 func (module *commonToSdkLibraryAndImport) setOutputFiles(ctx android.ModuleContext) {
 	if module.doctagPaths != nil {
 		ctx.SetOutputFiles(module.doctagPaths, ".doctags")
@@ -1751,30 +1709,13 @@
 	return latestPrebuiltApiModuleName(module.distStem()+"-incompatibilities", apiScope)
 }
 
-func (module *SdkLibrary) contributesToApiSurface(c android.Config) bool {
-	_, exists := c.GetApiLibraries()[module.Name()]
-	return exists
-}
-
-// The listed modules are the special java_sdk_libraries where apiScope.kind do not match the
-// api surface that the module contribute to. For example, the public droidstubs and java_library
-// do not contribute to the public api surface, but contributes to the core platform api surface.
-// This method returns the full api surface stub lib that
-// the generated java_api_library should depend on.
-func (module *SdkLibrary) alternativeFullApiSurfaceStubLib() string {
-	if val, ok := apiLibraryAdditionalProperties[module.Name()]; ok {
-		return val.FullApiSurfaceStubLib
-	}
-	return ""
-}
-
 // The listed modules' stubs contents do not match the corresponding txt files,
 // but require additional api contributions to generate the full stubs.
 // This method returns the name of the additional api contribution module
 // for corresponding sdk_library modules.
 func (module *SdkLibrary) apiLibraryAdditionalApiContribution() string {
 	if val, ok := apiLibraryAdditionalProperties[module.Name()]; ok {
-		return val.AdditionalApiContribution
+		return val
 	}
 	return ""
 }
@@ -2069,17 +2010,18 @@
 	mctx.CreateModule(DroidstubsFactory, &props, module.sdkComponentPropertiesForChildLibrary()).(*Droidstubs).CallHookIfAvailable(mctx)
 }
 
-func (module *SdkLibrary) createApiLibrary(mctx android.DefaultableHookContext, apiScope *apiScope, alternativeFullApiSurfaceStub string) {
+func (module *SdkLibrary) createApiLibrary(mctx android.DefaultableHookContext, apiScope *apiScope) {
 	props := struct {
-		Name                  *string
-		Visibility            []string
-		Api_contributions     []string
-		Libs                  []string
-		Static_libs           []string
-		Full_api_surface_stub *string
-		System_modules        *string
-		Enable_validation     *bool
-		Stubs_type            *string
+		Name              *string
+		Visibility        []string
+		Api_contributions []string
+		Libs              []string
+		Static_libs       []string
+		System_modules    *string
+		Enable_validation *bool
+		Stubs_type        *string
+		Sdk_version       *string
+		Previous_api      *string
 	}{}
 
 	props.Name = proptools.StringPtr(module.apiLibraryModuleName(apiScope))
@@ -2103,34 +2045,29 @@
 	}
 
 	props.Api_contributions = apiContributions
-	props.Libs = module.properties.Libs
+
+	// Ensure that stub-annotations is added to the classpath before any other libs
+	props.Libs = []string{"stub-annotations"}
+	props.Libs = append(props.Libs, module.properties.Libs...)
+	props.Libs = append(props.Libs, module.properties.Static_libs...)
 	props.Libs = append(props.Libs, module.sdkLibraryProperties.Stub_only_libs...)
 	props.Libs = append(props.Libs, module.scopeToProperties[apiScope].Libs...)
-	props.Libs = append(props.Libs, "stub-annotations")
 	props.Static_libs = module.sdkLibraryProperties.Stub_only_static_libs
-	props.Full_api_surface_stub = proptools.StringPtr(apiScope.kind.DefaultJavaLibraryName())
-	if alternativeFullApiSurfaceStub != "" {
-		props.Full_api_surface_stub = proptools.StringPtr(alternativeFullApiSurfaceStub)
-	}
-
-	// android_module_lib_stubs_current.from-text only comprises api contributions from art, conscrypt and i18n.
-	// Thus, replace with android_module_lib_stubs_current_full.from-text, which comprises every api domains.
-	if apiScope.kind == android.SdkModule {
-		props.Full_api_surface_stub = proptools.StringPtr(apiScope.kind.DefaultJavaLibraryName() + "_full.from-text")
-	}
-
-	// java_sdk_library modules that set sdk_version as none does not depend on other api
-	// domains. Therefore, java_api_library created from such modules should not depend on
-	// full_api_surface_stubs but create and compile stubs by the java_api_library module
-	// itself.
-	if module.SdkVersion(mctx).Kind == android.SdkNone {
-		props.Full_api_surface_stub = nil
-	}
 
 	props.System_modules = module.deviceProperties.System_modules
 	props.Enable_validation = proptools.BoolPtr(true)
 	props.Stubs_type = proptools.StringPtr("everything")
 
+	if module.deviceProperties.Sdk_version != nil {
+		props.Sdk_version = module.deviceProperties.Sdk_version
+	}
+
+	if module.compareAgainstLatestApi(apiScope) {
+		// check against the latest released API
+		latestApiFilegroupName := proptools.StringPtr(module.latestApiFilegroupName(apiScope))
+		props.Previous_api = latestApiFilegroupName
+	}
+
 	mctx.CreateModule(ApiLibraryFactory, &props, module.sdkComponentPropertiesForChildLibrary())
 }
 
@@ -2161,7 +2098,7 @@
 }
 
 func (module *SdkLibrary) createTopLevelStubsLibrary(
-	mctx android.DefaultableHookContext, apiScope *apiScope, contributesToApiSurface bool) {
+	mctx android.DefaultableHookContext, apiScope *apiScope) {
 
 	// Dist the "everything" stubs when the RELEASE_HIDDEN_API_EXPORTABLE_STUBS build flag is false
 	doDist := !mctx.Config().ReleaseHiddenApiExportableStubs()
@@ -2170,7 +2107,7 @@
 
 	// Add the stub compiling java_library/java_api_library as static lib based on build config
 	staticLib := module.sourceStubsLibraryModuleName(apiScope)
-	if mctx.Config().BuildFromTextStub() && contributesToApiSurface {
+	if mctx.Config().BuildFromTextStub() && module.ModuleBuildFromTextStubs() {
 		staticLib = module.apiLibraryModuleName(apiScope)
 	}
 	props.Static_libs = append(props.Static_libs, staticLib)
@@ -2213,8 +2150,8 @@
 	return module.uniqueApexVariations()
 }
 
-func (module *SdkLibrary) ContributeToApi() bool {
-	return proptools.BoolDefault(module.sdkLibraryProperties.Contribute_to_android_api, false)
+func (module *SdkLibrary) ModuleBuildFromTextStubs() bool {
+	return proptools.BoolDefault(module.sdkLibraryProperties.Build_from_text_stub, true)
 }
 
 // Creates the xml file that publicizes the runtime library
@@ -2390,16 +2327,10 @@
 		module.createStubsLibrary(mctx, scope)
 		module.createExportableStubsLibrary(mctx, scope)
 
-		alternativeFullApiSurfaceStubLib := ""
-		if scope == apiScopePublic {
-			alternativeFullApiSurfaceStubLib = module.alternativeFullApiSurfaceStubLib()
+		if mctx.Config().BuildFromTextStub() && module.ModuleBuildFromTextStubs() {
+			module.createApiLibrary(mctx, scope)
 		}
-		contributesToApiSurface := module.contributesToApiSurface(mctx.Config()) || alternativeFullApiSurfaceStubLib != ""
-		if contributesToApiSurface {
-			module.createApiLibrary(mctx, scope, alternativeFullApiSurfaceStubLib)
-		}
-
-		module.createTopLevelStubsLibrary(mctx, scope, contributesToApiSurface)
+		module.createTopLevelStubsLibrary(mctx, scope)
 		module.createTopLevelExportableStubsLibrary(mctx, scope)
 	}
 
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index a8a1494..911e8b1 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -35,9 +35,6 @@
 			"29": {"foo"},
 			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
 		}),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.BuildFlags = map[string]string{
 				"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
@@ -923,6 +920,7 @@
 	}
 
 	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`all_apex_contributions`,
 		`dex2oatd`,
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source.test`,
@@ -1588,9 +1586,6 @@
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
@@ -1609,36 +1604,30 @@
 	`)
 
 	testCases := []struct {
-		scope              *apiScope
-		apiContributions   []string
-		fullApiSurfaceStub string
+		scope            *apiScope
+		apiContributions []string
 	}{
 		{
-			scope:              apiScopePublic,
-			apiContributions:   []string{"foo.stubs.source.api.contribution"},
-			fullApiSurfaceStub: "android_stubs_current",
+			scope:            apiScopePublic,
+			apiContributions: []string{"foo.stubs.source.api.contribution"},
 		},
 		{
-			scope:              apiScopeSystem,
-			apiContributions:   []string{"foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
-			fullApiSurfaceStub: "android_system_stubs_current",
+			scope:            apiScopeSystem,
+			apiContributions: []string{"foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
 		},
 		{
-			scope:              apiScopeTest,
-			apiContributions:   []string{"foo.stubs.source.test.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
-			fullApiSurfaceStub: "android_test_stubs_current",
+			scope:            apiScopeTest,
+			apiContributions: []string{"foo.stubs.source.test.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
 		},
 		{
-			scope:              apiScopeModuleLib,
-			apiContributions:   []string{"foo.stubs.source.module_lib.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
-			fullApiSurfaceStub: "android_module_lib_stubs_current_full.from-text",
+			scope:            apiScopeModuleLib,
+			apiContributions: []string{"foo.stubs.source.module_lib.api.contribution", "foo.stubs.source.system.api.contribution", "foo.stubs.source.api.contribution"},
 		},
 	}
 
 	for _, c := range testCases {
 		m := result.ModuleForTests(c.scope.apiLibraryModuleName("foo"), "android_common").Module().(*ApiLibrary)
 		android.AssertArrayString(t, "Module expected to contain api contributions", c.apiContributions, m.properties.Api_contributions)
-		android.AssertStringEquals(t, "Module expected to contain full api surface api library", c.fullApiSurfaceStub, *m.properties.Full_api_surface_stub)
 	}
 }
 
@@ -1708,9 +1697,6 @@
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.SetApiLibraries([]string{"foo"})
-		}),
 	).RunTestWithBp(t, `
 		aconfig_declarations {
 			name: "bar",
diff --git a/java/system_modules.go b/java/system_modules.go
index 8e2d5d8..500d7fa 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -162,8 +162,9 @@
 	var jars android.Paths
 
 	ctx.VisitDirectDepsWithTag(systemModulesLibsTag, func(module android.Module) {
-		dep, _ := android.OtherModuleProvider(ctx, module, JavaInfoProvider)
-		jars = append(jars, dep.HeaderJars...)
+		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+			jars = append(jars, dep.HeaderJars...)
+		}
 	})
 
 	system.headerJars = jars
diff --git a/java/testing.go b/java/testing.go
index 7a42e4c..a99baf8 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -486,21 +486,17 @@
 	}
 
 	extraApiLibraryModules := map[string]droidstubsStruct{
-		"android_stubs_current.from-text":                  publicDroidstubs,
-		"android_system_stubs_current.from-text":           systemDroidstubs,
-		"android_test_stubs_current.from-text":             testDroidstubs,
-		"android_module_lib_stubs_current.from-text":       moduleLibDroidstubs,
-		"android_module_lib_stubs_current_full.from-text":  moduleLibDroidstubs,
-		"android_system_server_stubs_current.from-text":    systemServerDroidstubs,
-		"core.current.stubs.from-text":                     publicDroidstubs,
-		"legacy.core.platform.api.stubs.from-text":         publicDroidstubs,
-		"stable.core.platform.api.stubs.from-text":         publicDroidstubs,
-		"core-lambda-stubs.from-text":                      publicDroidstubs,
-		"android-non-updatable.stubs.from-text":            publicDroidstubs,
-		"android-non-updatable.stubs.system.from-text":     systemDroidstubs,
-		"android-non-updatable.stubs.test.from-text":       testDroidstubs,
-		"android-non-updatable.stubs.module_lib.from-text": moduleLibDroidstubs,
-		"android-non-updatable.stubs.test_module_lib":      moduleLibDroidstubs,
+		"android_stubs_current.from-text":                 publicDroidstubs,
+		"android_system_stubs_current.from-text":          systemDroidstubs,
+		"android_test_stubs_current.from-text":            testDroidstubs,
+		"android_module_lib_stubs_current.from-text":      moduleLibDroidstubs,
+		"android_module_lib_stubs_current_full.from-text": moduleLibDroidstubs,
+		"android_system_server_stubs_current.from-text":   systemServerDroidstubs,
+		"core.current.stubs.from-text":                    publicDroidstubs,
+		"legacy.core.platform.api.stubs.from-text":        publicDroidstubs,
+		"stable.core.platform.api.stubs.from-text":        publicDroidstubs,
+		"core-lambda-stubs.from-text":                     publicDroidstubs,
+		"android-non-updatable.stubs.test_module_lib":     moduleLibDroidstubs,
 	}
 
 	for _, droidstubs := range droidstubsStructs {
@@ -529,6 +525,8 @@
 			name: "%s",
 			api_contributions: ["%s"],
 			stubs_type: "everything",
+			sdk_version: "none",
+			system_modules: "none",
 		}
         `, libName, droidstubs.name+".api.contribution")
 	}
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index c0d4735..2bd246d 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -129,16 +129,16 @@
     print(f"ro.product.{partition}.name={config['DeviceProduct']}")
 
   if partition != "system":
-    if config["ModelForAttestation"]:
-        print(f"ro.product.model_for_attestation={config['ModelForAttestation']}")
-    if config["BrandForAttestation"]:
-        print(f"ro.product.brand_for_attestation={config['BrandForAttestation']}")
-    if config["NameForAttestation"]:
-        print(f"ro.product.name_for_attestation={config['NameForAttestation']}")
-    if config["DeviceForAttestation"]:
-        print(f"ro.product.device_for_attestation={config['DeviceForAttestation']}")
-    if config["ManufacturerForAttestation"]:
-        print(f"ro.product.manufacturer_for_attestation={config['ManufacturerForAttestation']}")
+    if config["ProductModelForAttestation"]:
+        print(f"ro.product.model_for_attestation={config['ProductModelForAttestation']}")
+    if config["ProductBrandForAttestation"]:
+        print(f"ro.product.brand_for_attestation={config['ProductBrandForAttestation']}")
+    if config["ProductNameForAttestation"]:
+        print(f"ro.product.name_for_attestation={config['ProductNameForAttestation']}")
+    if config["ProductDeviceForAttestation"]:
+        print(f"ro.product.device_for_attestation={config['ProductDeviceForAttestation']}")
+    if config["ProductManufacturerForAttestation"]:
+        print(f"ro.product.manufacturer_for_attestation={config['ProductManufacturerForAttestation']}")
 
   if config["ZygoteForce64"]:
     if partition == "vendor":
@@ -511,6 +511,15 @@
 
   build_prop(args, gen_build_info=True, gen_common_build_props=True, variables=variables)
 
+def build_system_ext_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = ["PRODUCT_SYSTEM_EXT_PROPERTIES"]
+
+  build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
+
 '''
 def build_vendor_prop(args):
   config = args.config
@@ -563,6 +572,8 @@
   with contextlib.redirect_stdout(args.out):
     if args.partition == "system":
       build_system_prop(args)
+    elif args.partition == "system_ext":
+      build_system_ext_prop(args)
       '''
     elif args.partition == "vendor":
       build_vendor_prop(args)
diff --git a/tests/sbom_test.sh b/tests/sbom_test.sh
index 794003d..0471853 100755
--- a/tests/sbom_test.sh
+++ b/tests/sbom_test.sh
@@ -76,8 +76,8 @@
   mkdir -p $sbom_test
   cp $product_out/*.img $sbom_test
 
-  # m sbom soong-sbom
-  run_soong "${out_dir}" "sbom soong-sbom"
+  # m sbom
+  run_soong "${out_dir}" "sbom"
 
   # Generate installed file list from .img files in PRODUCT_OUT
   dump_erofs=$out_dir/host/linux-x86/bin/dump.erofs
@@ -118,7 +118,6 @@
   for f in $EROFS_IMAGES; do
     partition_name=$(basename $f | cut -d. -f1)
     file_list_file="${sbom_test}/sbom-${partition_name}-files.txt"
-    files_in_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-spdx.txt"
     files_in_soong_spdx_file="${sbom_test}/soong-sbom-${partition_name}-files-in-spdx.txt"
     rm "$file_list_file" > /dev/null 2>&1 || true
     all_dirs="/"
@@ -147,34 +146,22 @@
     done
     sort -n -o "$file_list_file" "$file_list_file"
 
-    # Diff the file list from image and file list in SBOM created by Make
-    grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' > "$files_in_spdx_file"
-    if [ "$partition_name" = "system" ]; then
-      # system partition is mounted to /, so include FileName starts with /root/ too.
-      grep "FileName: /root/" $product_out/sbom.spdx | sed 's/^FileName: \/root//' >> "$files_in_spdx_file"
-    fi
-    sort -n -o "$files_in_spdx_file" "$files_in_spdx_file"
-
-    echo ============ Diffing files in $f and SBOM
-    diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name" ""
-
     # Diff the file list from image and file list in SBOM created by Soong
     grep "FileName: /${partition_name}/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: //' > "$files_in_soong_spdx_file"
-        if [ "$partition_name" = "system" ]; then
-          # system partition is mounted to /, so include FileName starts with /root/ too.
-          grep "FileName: /root/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: \/root//' >> "$files_in_soong_spdx_file"
-        fi
-        sort -n -o "$files_in_soong_spdx_file" "$files_in_soong_spdx_file"
+    if [ "$partition_name" = "system" ]; then
+      # system partition is mounted to /, so include FileName starts with /root/ too.
+      grep "FileName: /root/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: \/root//' >> "$files_in_soong_spdx_file"
+    fi
+    sort -n -o "$files_in_soong_spdx_file" "$files_in_soong_spdx_file"
 
-        echo ============ Diffing files in $f and SBOM created by Soong
-        diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
+    echo ============ Diffing files in $f and SBOM created by Soong
+    diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
   done
 
   RAMDISK_IMAGES="$product_out/ramdisk.img"
   for f in $RAMDISK_IMAGES; do
     partition_name=$(basename $f | cut -d. -f1)
     file_list_file="${sbom_test}/sbom-${partition_name}-files.txt"
-    files_in_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-spdx.txt"
     files_in_soong_spdx_file="${sbom_test}/sbom-${partition_name}-files-in-soong-spdx.txt"
     # lz4 decompress $f to stdout
     # cpio list all entries like ls -l
@@ -183,18 +170,12 @@
     # sed remove partition name from entry names
     $lz4 -c -d $f | cpio -tv 2>/dev/null | grep '^[-l]' | awk -F ' ' '{print $9}' | sed "s:^:/$partition_name/:" | sort -n > "$file_list_file"
 
-    grep "FileName: /${partition_name}/" $product_out/sbom.spdx | sed 's/^FileName: //' | sort -n > "$files_in_spdx_file"
-
     grep "FileName: /${partition_name}/" $soong_sbom_out/sbom.spdx | sed 's/^FileName: //' | sort -n > "$files_in_soong_spdx_file"
 
-    echo ============ Diffing files in $f and SBOM
-    diff_files "$file_list_file" "$files_in_spdx_file" "$partition_name" ""
-
     echo ============ Diffing files in $f and SBOM created by Soong
     diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
   done
 
-  verify_package_verification_code "$product_out/sbom.spdx"
   verify_package_verification_code "$soong_sbom_out/sbom.spdx"
 
   verify_packages_licenses "$soong_sbom_out/sbom.spdx"
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index f9622d3..ef18131 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -6,6 +6,7 @@
 	"encoding/json"
 	"fmt"
 	"io"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -174,6 +175,20 @@
 		return false
 	}
 
+	var extra_derived_suites []string
+	// Ensure all suites listed are also in base.
+	for _, s := range m.tradefedProperties.Test_suites {
+		if !slices.Contains(m.provider.TestSuites, s) {
+			extra_derived_suites = append(extra_derived_suites, s)
+		}
+	}
+	if len(extra_derived_suites) != 0 {
+		ctx.ModuleErrorf("Suites: [%s] listed but do not exist in base module: %s",
+			strings.Join(extra_derived_suites, ", "),
+			*m.tradefedProperties.Base)
+		return false
+	}
+
 	return true
 }
 
diff --git a/tradefed_modules/test_module_config_test.go b/tradefed_modules/test_module_config_test.go
index 97179f5..1510a03 100644
--- a/tradefed_modules/test_module_config_test.go
+++ b/tradefed_modules/test_module_config_test.go
@@ -40,6 +40,7 @@
 			name: "base",
 			sdk_version: "current",
                         data: [":HelperApp", "data/testfile"],
+                        test_suites: ["general-tests"],
 		}
 
                 test_module_config {
@@ -160,7 +161,7 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
 	).ExtendWithErrorHandler(
-		android.FixtureExpectsOneErrorPattern("'base' module used as base but it is not a 'android_test' module.")).
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern("'base' module used as base but it is not a 'android_test' module.")).
 		RunTestWithBp(t, badBp)
 }
 
@@ -193,6 +194,7 @@
 			name: "base",
 			sdk_version: "current",
                         srcs: ["a.java"],
+                        test_suites: ["general-tests"],
 		}
 
                 test_module_config {
@@ -218,6 +220,7 @@
 			sdk_version: "current",
                         srcs: ["a.java"],
                         data: [":HelperApp", "data/testfile"],
+                        test_suites: ["general-tests"],
 		}
 
                 android_test_helper_app {
@@ -362,6 +365,31 @@
 		RunTestWithBp(t, badBp)
 }
 
+func TestModuleConfigNonMatchingTestSuitesGiveErrors(t *testing.T) {
+	badBp := `
+		java_test_host {
+			name: "base",
+                        srcs: ["a.java"],
+                        test_suites: ["general-tests", "some-compat"],
+		}
+
+                test_module_config_host {
+                        name: "derived_test",
+                        base: "base",
+                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                        test_suites: ["device-tests", "random-suite"],
+                }`
+
+	android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).ExtendWithErrorHandler(
+		// Use \\ to escape bracket so it isn't used as [] set for regex.
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern("Suites: \\[device-tests, random-suite] listed but do not exist in base module")).
+		RunTestWithBp(t, badBp)
+}
+
 func TestTestOnlyProvider(t *testing.T) {
 	t.Parallel()
 	ctx := android.GroupFixturePreparers(
@@ -389,6 +417,7 @@
 			name: "base",
 			sdk_version: "current",
                         data: ["data/testfile"],
+                        test_suites: ["general-tests"],
 		}
 
 		java_test_host {