Merge "Switch to JDK 21" into main
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
index 7d7296e..3d15ac9 100644
--- a/aconfig/codegen/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -92,12 +92,12 @@
 	if !isModeSupported(mode) {
 		ctx.PropertyErrorf("mode", "%q is not a supported mode", mode)
 	}
-	// TODO: uncomment this part after internal clean up
-	//if mode == "exported" && !declarations.Exportable {
-	//	// if mode is exported, the corresponding aconfig_declaration must mark its
-	//	// exportable property true
-	//	ctx.PropertyErrorf("mode", "exported mode requires its aconfig_declaration has exportable prop true")
-	//}
+
+	if mode == "exported" && !declarations.Exportable {
+		// if mode is exported, the corresponding aconfig_declaration must mark its
+		// exportable property true
+		ctx.PropertyErrorf("mode", "exported mode requires its aconfig_declaration has exportable prop true")
+	}
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        javaRule,
diff --git a/android/Android.bp b/android/Android.bp
index e73f355..02b7444 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -135,6 +135,7 @@
         "rule_builder_test.go",
         "sdk_version_test.go",
         "sdk_test.go",
+        "selects_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
         "util_test.go",
diff --git a/android/api_levels.go b/android/api_levels.go
index 6fa4a0e..1130c3e 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/starlark_import"
 	"encoding/json"
 	"fmt"
 	"strconv"
@@ -440,7 +439,28 @@
 }
 
 func getApiLevelsMapReleasedVersions() (map[string]int, error) {
-	return starlark_import.GetStarlarkValue[map[string]int]("api_levels_released_versions")
+	return map[string]int{
+		"G":              9,
+		"I":              14,
+		"J":              16,
+		"J-MR1":          17,
+		"J-MR2":          18,
+		"K":              19,
+		"L":              21,
+		"L-MR1":          22,
+		"M":              23,
+		"N":              24,
+		"N-MR1":          25,
+		"O":              26,
+		"O-MR1":          27,
+		"P":              28,
+		"Q":              29,
+		"R":              30,
+		"S":              31,
+		"S-V2":           32,
+		"Tiramisu":       33,
+		"UpsideDownCake": 34,
+	}, nil
 }
 
 var finalCodenamesMapKey = NewOnceKey("FinalCodenamesMap")
diff --git a/android/config.go b/android/config.go
index e757d50..396b685 100644
--- a/android/config.go
+++ b/android/config.go
@@ -91,8 +91,6 @@
 	ModuleActionsFile string
 	DocFile           string
 
-	MultitreeBuild bool
-
 	BuildFromSourceStub bool
 
 	EnsureAllowlistIntegrity bool
@@ -288,10 +286,6 @@
 
 	BuildMode SoongBuildMode
 
-	// If MultitreeBuild is true then this is one inner tree of a multitree
-	// build directed by the multitree orchestrator.
-	MultitreeBuild bool
-
 	// If testAllowNonExistentPaths is true then PathForSource and PathForModuleSrc won't error
 	// in tests when a path doesn't exist.
 	TestAllowNonExistentPaths bool
@@ -541,8 +535,6 @@
 		moduleListFile: cmdArgs.ModuleListFile,
 		fs:             pathtools.NewOsFs(absSrcDir),
 
-		MultitreeBuild: cmdArgs.MultitreeBuild,
-
 		buildFromSourceStub: cmdArgs.BuildFromSourceStub,
 	}
 
@@ -646,9 +638,12 @@
 		"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":           {},
@@ -2027,25 +2022,35 @@
 
 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_NETWORKSTACKGOOGLE",
+		"RELEASE_APEX_CONTRIBUTIONS_NEURALNETWORKS",
 		"RELEASE_APEX_CONTRIBUTIONS_ONDEVICEPERSONALIZATION",
 		"RELEASE_APEX_CONTRIBUTIONS_PERMISSION",
 		"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_TZDATA",
 		"RELEASE_APEX_CONTRIBUTIONS_UWB",
 		"RELEASE_APEX_CONTRIBUTIONS_WIFI",
 	}
diff --git a/android/module_context.go b/android/module_context.go
index 1cab630..3fc5d01 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -21,6 +21,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -212,6 +213,10 @@
 	// GenerateAndroidBuildActions.  If it is called then the struct will be written out and included in
 	// the module-info.json generated by Make, and Make will not generate its own data for this module.
 	ModuleInfoJSON() *ModuleInfoJSON
+
+	// EvaluateConfiguration makes ModuleContext a valid proptools.ConfigurableEvaluator, so this context
+	// can be used to evaluate the final value of Configurable properties.
+	EvaluateConfiguration(parser.SelectType, string) (string, bool)
 }
 
 type moduleContext struct {
@@ -714,3 +719,32 @@
 func (m *moduleContext) TargetRequiredModuleNames() []string {
 	return m.module.TargetRequiredModuleNames()
 }
+
+func (m *moduleContext) EvaluateConfiguration(ty parser.SelectType, condition string) (string, bool) {
+	switch ty {
+	case parser.SelectTypeReleaseVariable:
+		if v, ok := m.Config().productVariables.BuildFlags[condition]; ok {
+			return v, true
+		}
+		return "", false
+	case parser.SelectTypeProductVariable:
+		// TODO: Might add these on a case-by-case basis
+		m.ModuleErrorf("TODO(b/323382414): Product variables are not yet supported in selects")
+		return "", false
+	case parser.SelectTypeSoongConfigVariable:
+		parts := strings.Split(condition, ":")
+		namespace := parts[0]
+		variable := parts[1]
+		if n, ok := m.Config().productVariables.VendorVars[namespace]; ok {
+			if v, ok := n[variable]; ok {
+				return v, true
+			}
+		}
+		return "", false
+	case parser.SelectTypeVariant:
+		m.ModuleErrorf("TODO(b/323382414): Variants are not yet supported in selects")
+		return "", false
+	default:
+		panic("Should be unreachable")
+	}
+}
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 7a9e89c..5a94a0f 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -548,6 +548,10 @@
 	// This should be an error, unless one of the prebuilts has been explicitly declared in apex_contributions
 	var selectedPrebuilt Module
 	for _, moduleInFamily := range allModulesInFamily {
+		// Skip if this module is in a different namespace
+		if !moduleInFamily.ExportedToMake() {
+			continue
+		}
 		// Skip for the top-level java_sdk_library_(_import). This has some special cases that need to be addressed first.
 		// This does not run into non-determinism because PrebuiltPostDepsMutator also has the special case
 		if sdkLibrary, ok := moduleInFamily.(interface{ SdkLibraryName() *string }); ok && sdkLibrary.SdkLibraryName() != nil {
@@ -650,6 +654,17 @@
 	CreatedByJavaSdkLibraryName() *string
 }
 
+// Returns true if the prebuilt variant is disabled
+// e.g. for a cc_prebuilt_library_shared, this will return
+// - true for the static variant of the module
+// - false for the shared variant of the module
+//
+// Even though this is a cc_prebuilt_library_shared, we create both the variants today
+// https://source.corp.google.com/h/googleplex-android/platform/build/soong/+/e08e32b45a18a77bc3c3e751f730539b1b374f1b:cc/library.go;l=2113-2116;drc=2c4a9779cd1921d0397a12b3d3521f4c9b30d747;bpv=1;bpt=0
+func (p *Prebuilt) variantIsDisabled(ctx BaseMutatorContext, prebuilt Module) bool {
+	return p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0
+}
+
 // 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 {
@@ -664,7 +679,7 @@
 		return false
 	}
 	// If the prebuilt module is explicitly listed in the metadata module, use that
-	if isSelected(psi, prebuilt) {
+	if isSelected(psi, prebuilt) && !p.variantIsDisabled(ctx, prebuilt) {
 		return true
 	}
 
@@ -672,7 +687,7 @@
 	// fall back to the existing source vs prebuilt selection.
 	// TODO: Drop the fallback mechanisms
 
-	if p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0 {
+	if p.variantIsDisabled(ctx, prebuilt) {
 		return false
 	}
 
diff --git a/android/selects_test.go b/android/selects_test.go
new file mode 100644
index 0000000..dca3789
--- /dev/null
+++ b/android/selects_test.go
@@ -0,0 +1,282 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func TestSelects(t *testing.T) {
+	testCases := []struct {
+		name          string
+		bp            string
+		provider      selectsTestProvider
+		vendorVars    map[string]map[string]string
+		expectedError string
+	}{
+		{
+			name: "basic string list",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"c.cpp"},
+			},
+		},
+		{
+			name: "basic string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a.cpp",
+					"b": "b.cpp",
+					_: "c.cpp",
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("c.cpp"),
+			},
+		},
+		{
+			name: "basic bool",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": true,
+					"b": false,
+					_: true,
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_bool: proptools.BoolPtr(true),
+			},
+		},
+		{
+			name: "Differing types",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a.cpp",
+					"b": true,
+					_: "c.cpp",
+				}),
+			}
+			`,
+			expectedError: `can't assign bool value to string property "my_string\[1\]"`,
+		},
+		{
+			name: "String list non-default",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
+			name: "String list append",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}) + select(soong_config_variable("my_namespace", "my_variable_2"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp", "c2.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
+			name: "String list prepend literal",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"literal.cpp", "c2.cpp"},
+			},
+		},
+		{
+			name: "String list append literal",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}) + ["literal.cpp"],
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"c2.cpp", "literal.cpp"},
+			},
+		},
+		{
+			name: "Can't append bools",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": true,
+					"b": false,
+					_: true,
+				}) + false,
+			}
+			`,
+			expectedError: "my_bool: Cannot append bools",
+		},
+		{
+			name: "Append string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a",
+					"b": "b",
+					_: "c",
+				}) + ".cpp",
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("c.cpp"),
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			fixtures := GroupFixturePreparers(
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("my_module_type", newSelectsMockModule)
+				}),
+				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+					variables.VendorVars = tc.vendorVars
+				}),
+			)
+			if tc.expectedError != "" {
+				fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
+			}
+			result := fixtures.RunTestWithBp(t, tc.bp)
+
+			if tc.expectedError == "" {
+				m := result.ModuleForTests("foo", "")
+				p, _ := OtherModuleProvider[selectsTestProvider](result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
+				if !reflect.DeepEqual(p, tc.provider) {
+					t.Errorf("Expected:\n  %q\ngot:\n  %q", tc.provider.String(), p.String())
+				}
+			}
+		})
+	}
+}
+
+type selectsTestProvider struct {
+	my_bool        *bool
+	my_string      *string
+	my_string_list *[]string
+}
+
+func (p *selectsTestProvider) String() string {
+	myBoolStr := "nil"
+	if p.my_bool != nil {
+		myBoolStr = fmt.Sprintf("%t", *p.my_bool)
+	}
+	myStringStr := "nil"
+	if p.my_string != nil {
+		myStringStr = *p.my_string
+	}
+	return fmt.Sprintf(`selectsTestProvider {
+	my_bool: %v,
+	my_string: %s,
+    my_string_list: %s,
+}`, myBoolStr, myStringStr, p.my_string_list)
+}
+
+var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
+
+type selectsMockModuleProperties struct {
+	My_bool        proptools.Configurable[bool]
+	My_string      proptools.Configurable[string]
+	My_string_list proptools.Configurable[[]string]
+}
+
+type selectsMockModule struct {
+	ModuleBase
+	DefaultableModuleBase
+	properties selectsMockModuleProperties
+}
+
+func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	SetProvider[selectsTestProvider](ctx, selectsTestProviderKey, selectsTestProvider{
+		my_bool:        p.properties.My_bool.Evaluate(ctx),
+		my_string:      p.properties.My_string.Evaluate(ctx),
+		my_string_list: p.properties.My_string_list.Evaluate(ctx),
+	})
+}
+
+func newSelectsMockModule() Module {
+	m := &selectsMockModule{}
+	m.AddProperties(&m.properties)
+	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
+	InitDefaultableModule(m)
+	return m
+}
diff --git a/android/updatable_modules.go b/android/updatable_modules.go
index 6d0eeb7..1548170 100644
--- a/android/updatable_modules.go
+++ b/android/updatable_modules.go
@@ -33,4 +33,4 @@
 // * AOSP            - xx9990000
 // * x-mainline-prod - xx9990000
 // * master          - 990090000
-const DefaultUpdatableModuleVersion = "340090000"
+const DefaultUpdatableModuleVersion = "990090000"
diff --git a/apex/apex.go b/apex/apex.go
index c6d8234..9d7af18 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2075,8 +2075,10 @@
 				return true // track transitive dependencies
 			case *java.AndroidAppImport:
 				vctx.filesInfo = append(vctx.filesInfo, apexFilesForAndroidApp(ctx, ap)...)
+				addAconfigFiles(vctx, ctx, child)
 			case *java.AndroidTestHelperApp:
 				vctx.filesInfo = append(vctx.filesInfo, apexFilesForAndroidApp(ctx, ap)...)
+				addAconfigFiles(vctx, ctx, child)
 			case *java.AndroidAppSet:
 				appDir := "app"
 				if ap.Privileged() {
@@ -2090,6 +2092,7 @@
 				af := newApexFile(ctx, ap.OutputFile(), ap.BaseModuleName(), appDirName, appSet, ap)
 				af.certificate = java.PresignedCertificate
 				vctx.filesInfo = append(vctx.filesInfo, af)
+				addAconfigFiles(vctx, ctx, child)
 			default:
 				ctx.PropertyErrorf("apps", "%q is not an android_app module", depName)
 			}
@@ -2118,6 +2121,7 @@
 		case prebuiltTag:
 			if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
 				vctx.filesInfo = append(vctx.filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
+				addAconfigFiles(vctx, ctx, child)
 			} else {
 				ctx.PropertyErrorf("prebuilts", "%q is not a prebuilt_etc module", depName)
 			}
@@ -2141,6 +2145,7 @@
 					af := apexFileForExecutable(ctx, ccTest)
 					af.class = nativeTest
 					vctx.filesInfo = append(vctx.filesInfo, af)
+					addAconfigFiles(vctx, ctx, child)
 				}
 				return true // track transitive dependencies
 			} else {
@@ -2230,11 +2235,13 @@
 			}
 
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		} else if rm, ok := child.(*rust.Module); ok {
 			af := apexFileForRustLibrary(ctx, rm)
 			af.transitiveDep = true
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		}
 	} else if cc.IsTestPerSrcDepTag(depTag) {
@@ -2263,6 +2270,7 @@
 			af := apexFileForRustLibrary(ctx, rustm)
 			af.transitiveDep = true
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		}
 	} else if rust.IsRlibDepTag(depTag) {
@@ -2281,6 +2289,7 @@
 				return false
 			}
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		default:
 			ctx.PropertyErrorf("bootclasspath_fragments",
@@ -2295,6 +2304,7 @@
 			if profileAf := apexFileForJavaModuleProfile(ctx, child.(javaModule)); profileAf != nil {
 				vctx.filesInfo = append(vctx.filesInfo, *profileAf)
 			}
+			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		default:
 			ctx.PropertyErrorf("systemserverclasspath_fragments",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 65f0a23..54d2d08 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -11120,10 +11120,10 @@
 		t.Fatalf("Expected 5 commands, got %d in:\n%s", len(copyCmds), s)
 	}
 
-	ensureMatches(t, copyCmds[4], "^cp -f .*/aconfig_flags.pb .*/image.apex$")
-	ensureMatches(t, copyCmds[5], "^cp -f .*/package.map .*/image.apex$")
-	ensureMatches(t, copyCmds[6], "^cp -f .*/flag.map .*/image.apex$")
-	ensureMatches(t, copyCmds[7], "^cp -f .*/flag.val .*/image.apex$")
+	ensureMatches(t, copyCmds[4], "^cp -f .*/aconfig_flags.pb .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[5], "^cp -f .*/package.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[6], "^cp -f .*/flag.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[7], "^cp -f .*/flag.val .*/image.apex/etc$")
 
 	inputs := []string{
 		"my_aconfig_declarations_foo/intermediate.pb",
@@ -11244,10 +11244,10 @@
 		t.Fatalf("Expected 12 commands, got %d in:\n%s", len(copyCmds), s)
 	}
 
-	ensureMatches(t, copyCmds[8], "^cp -f .*/aconfig_flags.pb .*/image.apex$")
-	ensureMatches(t, copyCmds[9], "^cp -f .*/package.map .*/image.apex$")
-	ensureMatches(t, copyCmds[10], "^cp -f .*/flag.map .*/image.apex$")
-	ensureMatches(t, copyCmds[11], "^cp -f .*/flag.val .*/image.apex$")
+	ensureMatches(t, copyCmds[8], "^cp -f .*/aconfig_flags.pb .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[9], "^cp -f .*/package.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[10], "^cp -f .*/flag.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[11], "^cp -f .*/flag.val .*/image.apex/etc$")
 
 	inputs := []string{
 		"my_aconfig_declarations_foo/intermediate.pb",
@@ -11385,13 +11385,15 @@
 		t.Fatalf("Expected 26 commands, got %d in:\n%s", len(copyCmds), s)
 	}
 
-	ensureMatches(t, copyCmds[22], "^cp -f .*/aconfig_flags.pb .*/image.apex$")
-	ensureMatches(t, copyCmds[23], "^cp -f .*/package.map .*/image.apex$")
-	ensureMatches(t, copyCmds[24], "^cp -f .*/flag.map .*/image.apex$")
-	ensureMatches(t, copyCmds[25], "^cp -f .*/flag.val .*/image.apex$")
+	ensureMatches(t, copyCmds[22], "^cp -f .*/aconfig_flags.pb .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[23], "^cp -f .*/package.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[24], "^cp -f .*/flag.map .*/image.apex/etc$")
+	ensureMatches(t, copyCmds[25], "^cp -f .*/flag.val .*/image.apex/etc$")
 
 	inputs := []string{
 		"my_aconfig_declarations_foo/intermediate.pb",
+		"my_aconfig_declarations_bar/intermediate.pb",
+		"my_aconfig_declarations_baz/intermediate.pb",
 		"my_rust_binary/android_arm64_armv8-a_apex10000/myapex/aconfig_merged.pb",
 	}
 	VerifyAconfigRule(t, &mod, "combine_aconfig_declarations", inputs, "android_common_myapex/aconfig_flags.pb", "", "")
diff --git a/apex/builder.go b/apex/builder.go
index e49cf28..6ad282a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -645,6 +645,7 @@
 	prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
 
 	defaultReadOnlyFiles := []string{"apex_manifest.json", "apex_manifest.pb"}
+	aconfigDest := imageDir.Join(ctx, "etc").String()
 	if len(a.aconfigFiles) > 0 {
 		apexAconfigFile := android.PathForModuleOut(ctx, "aconfig_flags.pb")
 		ctx.Build(pctx, android.BuildParams{
@@ -657,9 +658,9 @@
 			},
 		})
 
-		copyCommands = append(copyCommands, "cp -f "+apexAconfigFile.String()+" "+imageDir.String())
+		copyCommands = append(copyCommands, "cp -f "+apexAconfigFile.String()+" "+aconfigDest)
 		implicitInputs = append(implicitInputs, apexAconfigFile)
-		defaultReadOnlyFiles = append(defaultReadOnlyFiles, apexAconfigFile.Base())
+		defaultReadOnlyFiles = append(defaultReadOnlyFiles, "etc/"+apexAconfigFile.Base())
 
 		for _, info := range createStorageInfo {
 			outputFile := android.PathForModuleOut(ctx, info.Output_file)
@@ -675,9 +676,9 @@
 				},
 			})
 
-			copyCommands = append(copyCommands, "cp -f "+outputFile.String()+" "+imageDir.String())
+			copyCommands = append(copyCommands, "cp -f "+outputFile.String()+" "+aconfigDest)
 			implicitInputs = append(implicitInputs, outputFile)
-			defaultReadOnlyFiles = append(defaultReadOnlyFiles, outputFile.Base())
+			defaultReadOnlyFiles = append(defaultReadOnlyFiles, "etc/"+outputFile.Base())
 		}
 	}
 
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 8f4b7df..cbb5d58 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -260,6 +260,9 @@
 
 func (p *prebuiltLibraryLinker) disablePrebuilt() {
 	p.properties.Srcs = nil
+	p.properties.Sanitized.None.Srcs = nil
+	p.properties.Sanitized.Address.Srcs = nil
+	p.properties.Sanitized.Hwaddress.Srcs = nil
 }
 
 // Implements versionedInterface
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 7dfe642..71b7e43 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -702,3 +702,61 @@
 		android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from %s to %s\n", libfoo.Name(), tc.expectedDependencyName), true, hasDep(ctx, libfoo, expectedDependency))
 	}
 }
+
+// If module sdk cannot provide a cc module variant (e.g. static), then the module variant from source should be used
+func TestMissingVariantInModuleSdk(t *testing.T) {
+	bp := `
+		// an rdep
+		cc_library {
+			name: "libfoo",
+			static_libs: ["libbar"],
+		}
+
+		// source
+		cc_library {
+			name: "libbar",
+		}
+		// prebuilt
+		// libbar only exists as a shared library
+		cc_prebuilt_library_shared {
+			name: "libbar",
+			srcs: ["libbar.so"],
+		}
+		// selectors
+		apex_contributions {
+			name: "myapex_contributions",
+			contents: ["prebuilt_libbar"],
+		}
+		all_apex_contributions {name: "all_apex_contributions"}
+	`
+	hasDep := func(ctx *android.TestContext, m android.Module, wantDep android.Module) bool {
+		t.Helper()
+		var found bool
+		ctx.VisitDirectDeps(m, func(dep blueprint.Module) {
+			if dep == wantDep {
+				found = true
+			}
+		})
+		return found
+	}
+
+	preparer := android.GroupFixturePreparers(
+		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+			android.RegisterApexContributionsBuildComponents(ctx)
+		}),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.BuildFlags = map[string]string{
+				"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "myapex_contributions",
+			}
+		}),
+	)
+	ctx := testPrebuilt(t, bp, map[string][]byte{
+		"libbar.so": nil,
+		"crtx.o":    nil,
+	}, preparer)
+	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+	sourceLibBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Module()
+	// Even though the prebuilt is listed in apex_contributions, the prebuilt does not have a static variant.
+	// Therefore source of libbar should be used.
+	android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from libfoo to source libbar"), true, hasDep(ctx, libfoo, sourceLibBar))
+}
diff --git a/cc/stl.go b/cc/stl.go
index 63c23d7..de2066f 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -205,12 +205,14 @@
 			flags.extraLibFlags = append(flags.extraLibFlags, "-nostdlib++")
 			if ctx.Windows() {
 				flags.Local.CppFlags = append(flags.Local.CppFlags,
-					// Disable visiblity annotations since we're using static
-					// libc++.
-					"-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
-					"-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
+					// These macros can also be defined by libc++'s __config
+					// or __config_site headers so define them the same way
+					// (i.e. to nothing). Disable visibility annotations since
+					// we're using static libc++.
+					"-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS=",
+					"-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS=",
 					// Use Win32 threads in libc++.
-					"-D_LIBCPP_HAS_THREAD_API_WIN32")
+					"-D_LIBCPP_HAS_THREAD_API_WIN32=")
 			}
 		}
 	case "libstdc++":
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 673f305..d64010e 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -76,7 +76,6 @@
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
 	flag.StringVar(&cmdlineArgs.SoongVariables, "soong_variables", "soong.variables", "the file contains all build variables")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
-	flag.BoolVar(&cmdlineArgs.MultitreeBuild, "multitree-build", false, "this is a multitree build")
 	flag.BoolVar(&cmdlineArgs.BuildFromSourceStub, "build-from-source-stub", false, "build Java stubs from source files instead of API text files")
 	flag.BoolVar(&cmdlineArgs.EnsureAllowlistIntegrity, "ensure-allowlist-integrity", false, "verify that allowlisted modules are mixed-built")
 	flag.StringVar(&cmdlineArgs.ModuleDebugFile, "soong_module_debug", "", "soong module debug info file to write")
diff --git a/java/aapt2.go b/java/aapt2.go
index 445e912..f704fc6 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -309,7 +309,8 @@
 
 var aapt2ConvertRule = pctx.AndroidStaticRule("aapt2Convert",
 	blueprint.RuleParams{
-		Command:     `${config.Aapt2Cmd} convert --output-format $format $in -o $out`,
+		Command: `${config.Aapt2Cmd} convert --enable-compact-entries ` +
+			`--output-format $format $in -o $out`,
 		CommandDeps: []string{"${config.Aapt2Cmd}"},
 	}, "format",
 )
diff --git a/java/aar.go b/java/aar.go
index 0edee83..27dd38b 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -204,6 +204,8 @@
 	// Flags specified in Android.bp
 	linkFlags = append(linkFlags, a.aaptProperties.Aaptflags...)
 
+	linkFlags = append(linkFlags, "--enable-compact-entries")
+
 	// Find implicit or explicit asset and resource dirs
 	assets := android.PathsRelativeToModuleSourceDir(android.SourceInput{
 		Context:     ctx,
@@ -453,6 +455,11 @@
 		// as imports.  The resources from dependencies will not be merged into this module's package-res.apk, and
 		// instead modules depending on this module will reference package-res.apk from all transitive static
 		// dependencies.
+		for _, sharedDep := range sharedDeps {
+			if sharedDep.usedResourceProcessor {
+				transitiveRJars = append(transitiveRJars, sharedDep.rJar)
+			}
+		}
 		for _, staticDep := range staticDeps {
 			linkDeps = append(linkDeps, staticDep.resPackage)
 			linkFlags = append(linkFlags, "-I "+staticDep.resPackage.String())
@@ -460,11 +467,6 @@
 				transitiveRJars = append(transitiveRJars, staticDep.rJar)
 			}
 		}
-		for _, sharedDep := range sharedDeps {
-			if sharedDep.usedResourceProcessor {
-				transitiveRJars = append(transitiveRJars, sharedDep.rJar)
-			}
-		}
 	} else {
 		// When building an app or building a library without ResourceProcessorBusyBox enabled all static
 		// dependencies are compiled into this module's package-res.apk as overlays.
@@ -554,6 +556,10 @@
 	transitiveAaptResourcePackagesFile := android.PathForModuleOut(ctx, "transitive-res-packages")
 	android.WriteFileRule(ctx, transitiveAaptResourcePackagesFile, strings.Join(transitiveAaptResourcePackages, "\n"))
 
+	// Reverse the list of R.jar files so that the current module comes first, and direct dependencies come before
+	// transitive dependencies.
+	transitiveRJars = android.ReversePaths(transitiveRJars)
+
 	a.aaptSrcJar = srcJar
 	a.transitiveAaptRJars = transitiveRJars
 	a.transitiveAaptResourcePackagesFile = transitiveAaptResourcePackagesFile
@@ -1134,7 +1140,7 @@
 	extractedAARDir := android.PathForModuleOut(ctx, "aar")
 	a.classpathFile = extractedAARDir.Join(ctx, "classes-combined.jar")
 	a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
-	aarRTxt := extractedAARDir.Join(ctx, "R.txt")
+	a.rTxt = extractedAARDir.Join(ctx, "R.txt")
 	a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
 	a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
 	android.SetProvider(ctx, ProguardSpecInfoProvider, ProguardSpecInfo{
@@ -1148,7 +1154,7 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        unzipAAR,
 		Input:       a.aarPath,
-		Outputs:     android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest, a.assetsPackage, aarRTxt},
+		Outputs:     android.WritablePaths{a.classpathFile, a.proguardFlags, a.manifest, a.assetsPackage, a.rTxt},
 		Description: "unzip AAR",
 		Args: map[string]string{
 			"outDir":             extractedAARDir.String(),
@@ -1166,7 +1172,7 @@
 
 	a.exportPackage = android.PathForModuleOut(ctx, "package-res.apk")
 	proguardOptionsFile := android.PathForModuleGen(ctx, "proguard.options")
-	a.rTxt = android.PathForModuleOut(ctx, "R.txt")
+	aaptRTxt := android.PathForModuleOut(ctx, "R.txt")
 	a.extraAaptPackagesFile = android.PathForModuleOut(ctx, "extra_packages")
 
 	var linkDeps android.Paths
@@ -1203,7 +1209,7 @@
 	}
 
 	transitiveAssets := android.ReverseSliceInPlace(staticDeps.assets())
-	aapt2Link(ctx, a.exportPackage, nil, proguardOptionsFile, a.rTxt,
+	aapt2Link(ctx, a.exportPackage, nil, proguardOptionsFile, aaptRTxt,
 		linkFlags, linkDeps, nil, overlayRes, transitiveAssets, nil, nil)
 
 	a.rJar = android.PathForModuleOut(ctx, "busybox/R.jar")
diff --git a/java/androidmk.go b/java/androidmk.go
index b7df9bf..498962f 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -691,9 +691,10 @@
 		return nil
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
-		Class:      "APPS",
-		OutputFile: android.OptionalPathForPath(a.outputFile),
-		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
+		Class:        "APPS",
+		OutputFile:   android.OptionalPathForPath(a.outputFile),
+		OverrideName: a.BaseModuleName(), // TODO (spandandas): Add a test
+		Include:      "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetBoolIfTrue("LOCAL_PRIVILEGED_MODULE", a.Privileged())
diff --git a/java/app.go b/java/app.go
index 9736ffd..8209d4c 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1316,6 +1316,12 @@
 	a.extraTestConfigs = android.PathsForModuleSrc(ctx, a.testProperties.Test_options.Extra_test_configs)
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
 	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
+		InstalledFiles:          a.data,
+		OutputFile:              a.OutputFile(),
+		TestConfig:              a.testConfig,
+		HostRequiredModuleNames: a.HostRequiredModuleNames(),
+	})
 }
 
 func (a *AndroidTest) FixTestConfig(ctx android.ModuleContext, testConfig android.Path) android.Path {
diff --git a/java/app_import.go b/java/app_import.go
index 8c90e4c..dc84fc2 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -145,6 +145,11 @@
 	// Whether or not to skip checking the preprocessed apk for proper alignment and uncompressed
 	// JNI libs and dex files. Default is false
 	Skip_preprocessed_apk_checks *bool
+
+	// Name of the source soong module that gets shadowed by this prebuilt
+	// If unspecified, follows the naming convention that the source module of
+	// the prebuilt is Name() without "prebuilt_" prefix
+	Source_module_name *string
 }
 
 func (a *AndroidAppImport) IsInstallable() bool {
@@ -274,6 +279,10 @@
 	return a.BaseModuleName()
 }
 
+func (a *AndroidAppImport) BaseModuleName() string {
+	return proptools.StringDefault(a.properties.Source_module_name, a.ModuleBase.Name())
+}
+
 func (a *AndroidAppImport) generateAndroidBuildActions(ctx android.ModuleContext) {
 	if a.Name() == "prebuilt_framework-res" {
 		ctx.ModuleErrorf("prebuilt_framework-res found. This used to have special handling in soong, but was removed due to prebuilt_framework-res no longer existing. This check is to ensure it doesn't come back without readding the special handling.")
diff --git a/java/app_test.go b/java/app_test.go
index 125c971..5d7b048 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -931,8 +931,8 @@
 				"out/soong/.intermediates/direct_import/android_common/aar/classes-combined.jar",
 			},
 			appCombined: []string{
-				"out/soong/.intermediates/app/android_common/busybox/R.jar",
 				"out/soong/.intermediates/app/android_common/javac/app.jar",
+				"out/soong/.intermediates/app/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
 				"out/soong/.intermediates/direct_import/android_common/aar/classes-combined.jar",
 			},
@@ -948,10 +948,10 @@
 			directSrcJars: nil,
 			directClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
-				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
 				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
 				"out/soong/.intermediates/transitive_import/android_common/aar/classes-combined.jar",
 			},
@@ -981,9 +981,9 @@
 			sharedSrcJars: nil,
 			sharedClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
+				"out/soong/.intermediates/shared/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_static/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_shared/android_common/busybox/R.jar",
-				"out/soong/.intermediates/shared/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_shared/android_common/turbine-combined/shared_transitive_shared.jar",
 				"out/soong/.intermediates/shared_transitive_static/android_common/turbine-combined/shared_transitive_static.jar",
 			},
@@ -1037,8 +1037,8 @@
 				"out/soong/.intermediates/direct_import/android_common/aar/classes-combined.jar",
 			},
 			appCombined: []string{
-				"out/soong/.intermediates/app/android_common/busybox/R.jar",
 				"out/soong/.intermediates/app/android_common/javac/app.jar",
+				"out/soong/.intermediates/app/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/combined/direct.jar",
 				"out/soong/.intermediates/direct_import/android_common/aar/classes-combined.jar",
 			},
@@ -1094,9 +1094,9 @@
 			directSrcJars: nil,
 			directClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
-				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
 				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
 				"out/soong/.intermediates/transitive_import/android_common/aar/classes-combined.jar",
 			},
diff --git a/java/base.go b/java/base.go
index 2b499e2..d8ccec6 100644
--- a/java/base.go
+++ b/java/base.go
@@ -95,6 +95,9 @@
 	// if not blank, used as prefix to generate repackage rule
 	Jarjar_prefix *string
 
+	// if set to true, skip the jarjar repackaging
+	Skip_jarjar_repackage *bool
+
 	// If not blank, set the java version passed to javac as -source and -target
 	Java_version *string
 
@@ -1101,11 +1104,13 @@
 	jarjarProviderData := j.collectJarJarRules(ctx)
 	if jarjarProviderData != nil {
 		android.SetProvider(ctx, JarJarProvider, *jarjarProviderData)
-		text := getJarJarRuleText(jarjarProviderData)
-		if text != "" {
-			ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
-			android.WriteFileRule(ctx, ruleTextFile, text)
-			j.repackageJarjarRules = ruleTextFile
+		if !proptools.Bool(j.properties.Skip_jarjar_repackage) {
+			text := getJarJarRuleText(jarjarProviderData)
+			if text != "" {
+				ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
+				android.WriteFileRule(ctx, ruleTextFile, text)
+				j.repackageJarjarRules = ruleTextFile
+			}
 		}
 	}
 
@@ -1307,7 +1312,7 @@
 		}
 	}
 
-	jars := append(android.Paths(nil), kotlinJars...)
+	jars := slices.Clone(kotlinJars)
 
 	j.compiledSrcJars = srcJars
 
@@ -1322,7 +1327,7 @@
 			// allow for the use of annotation processors that do function correctly
 			// with sharding enabled. See: b/77284273.
 		}
-		extraJars := append(android.CopyOf(extraCombinedJars), kotlinHeaderJars...)
+		extraJars := append(slices.Clone(kotlinHeaderJars), extraCombinedJars...)
 		headerJarFileWithoutDepsOrJarjar, j.headerJarFile, j.repackagedHeaderJarFile =
 			j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName, extraJars)
 		if ctx.Failed() {
@@ -1396,6 +1401,8 @@
 		}
 	}
 
+	jars = append(jars, extraCombinedJars...)
+
 	j.srcJarArgs, j.srcJarDeps = resourcePathsToJarArgs(srcFiles), srcFiles
 
 	var includeSrcJar android.WritablePath
@@ -1482,8 +1489,6 @@
 		jars = append(jars, servicesJar)
 	}
 
-	jars = append(android.CopyOf(extraCombinedJars), jars...)
-
 	// 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
diff --git a/java/builder.go b/java/builder.go
index 74a05f2..b07a622 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -212,6 +212,14 @@
 			CommandDeps: []string{"${config.MergeZipsCmd}"},
 		},
 		"jarArgs")
+	combineJarRsp = pctx.AndroidStaticRule("combineJarRsp",
+		blueprint.RuleParams{
+			Command:        `${config.MergeZipsCmd} --ignore-duplicates -j $jarArgs $out @$out.rsp`,
+			CommandDeps:    []string{"${config.MergeZipsCmd}"},
+			Rspfile:        "$out.rsp",
+			RspfileContent: "$in",
+		},
+		"jarArgs")
 
 	jarjar = pctx.AndroidStaticRule("jarjar",
 		blueprint.RuleParams{
@@ -418,7 +426,7 @@
 		})
 }
 
-func turbineFlags(ctx android.ModuleContext, flags javaBuilderFlags) (string, android.Paths) {
+func turbineFlags(ctx android.ModuleContext, flags javaBuilderFlags, dir string) (string, android.Paths) {
 	var deps android.Paths
 
 	classpath := flags.classpath
@@ -443,13 +451,21 @@
 	deps = append(deps, classpath...)
 	turbineFlags := bootClasspath + " " + classpath.FormTurbineClassPath("--classpath ")
 
+	const flagsLimit = 32 * 1024
+	if len(turbineFlags) > flagsLimit {
+		flagsRspFile := android.PathForModuleOut(ctx, dir, "turbine-flags.rsp")
+		android.WriteFileRule(ctx, flagsRspFile, turbineFlags)
+		turbineFlags = "@" + flagsRspFile.String()
+		deps = append(deps, flagsRspFile)
+	}
+
 	return turbineFlags, deps
 }
 
 func TransformJavaToHeaderClasses(ctx android.ModuleContext, outputFile android.WritablePath,
 	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
 
-	turbineFlags, deps := turbineFlags(ctx, flags)
+	turbineFlags, deps := turbineFlags(ctx, flags, "turbine")
 
 	deps = append(deps, srcJars...)
 
@@ -481,7 +497,7 @@
 func TurbineApt(ctx android.ModuleContext, outputSrcJar, outputResJar android.WritablePath,
 	srcFiles, srcJars android.Paths, flags javaBuilderFlags) {
 
-	turbineFlags, deps := turbineFlags(ctx, flags)
+	turbineFlags, deps := turbineFlags(ctx, flags, "kapt")
 
 	deps = append(deps, srcJars...)
 
@@ -534,14 +550,14 @@
 
 	deps = append(deps, srcJars...)
 
-	classpath := flags.classpath
+	javacClasspath := flags.classpath
 
 	var bootClasspath string
 	if flags.javaVersion.usesJavaModules() {
 		var systemModuleDeps android.Paths
 		bootClasspath, systemModuleDeps = flags.systemModules.FormJavaSystemModulesPath(ctx.Device())
 		deps = append(deps, systemModuleDeps...)
-		classpath = append(flags.java9Classpath, classpath...)
+		javacClasspath = append(flags.java9Classpath, javacClasspath...)
 	} else {
 		deps = append(deps, flags.bootClasspath...)
 		if len(flags.bootClasspath) == 0 && ctx.Device() {
@@ -553,7 +569,19 @@
 		}
 	}
 
-	deps = append(deps, classpath...)
+	classpathArg := javacClasspath.FormJavaClassPath("-classpath")
+
+	// Keep the command line under the MAX_ARG_STRLEN limit by putting the classpath argument into an rsp file
+	// if it is too long.
+	const classpathLimit = 64 * 1024
+	if len(classpathArg) > classpathLimit {
+		classpathRspFile := outputFile.ReplaceExtension(ctx, "classpath")
+		android.WriteFileRule(ctx, classpathRspFile, classpathArg)
+		deps = append(deps, classpathRspFile)
+		classpathArg = "@" + classpathRspFile.String()
+	}
+
+	deps = append(deps, javacClasspath...)
 	deps = append(deps, flags.processorPath...)
 
 	processor := "-proc:none"
@@ -584,7 +612,7 @@
 		Args: map[string]string{
 			"javacFlags":    flags.javacFlags,
 			"bootClasspath": bootClasspath,
-			"classpath":     classpath.FormJavaClassPath("-classpath"),
+			"classpath":     classpathArg,
 			"processorpath": flags.processorPath.FormJavaClassPath("-processorpath"),
 			"processor":     processor,
 			"srcJars":       strings.Join(srcJars.Strings(), " "),
@@ -643,8 +671,23 @@
 		jarArgs = append(jarArgs, "-D")
 	}
 
+	rule := combineJar
+	// Keep the command line under the MAX_ARG_STRLEN limit by putting the list of jars into an rsp file
+	// if it is too long.
+	const jarsLengthLimit = 64 * 1024
+	jarsLength := 0
+	for i, jar := range jars {
+		if i != 0 {
+			jarsLength += 1
+		}
+		jarsLength += len(jar.String())
+	}
+	if jarsLength > jarsLengthLimit {
+		rule = combineJarRsp
+	}
+
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        combineJar,
+		Rule:        rule,
 		Description: desc,
 		Output:      outputFile,
 		Inputs:      jars,
diff --git a/java/config/droidstubs.go b/java/config/droidstubs.go
index f46c893..39eec44 100644
--- a/java/config/droidstubs.go
+++ b/java/config/droidstubs.go
@@ -23,7 +23,6 @@
 		"--format=v2",
 		"--repeat-errors-max 10",
 		"--hide UnresolvedImport",
-		"--hide InvalidNullabilityOverride",
 
 		// Force metalava to ignore classes on the classpath when an API file contains missing classes.
 		// See b/285140653 for more information.
@@ -49,9 +48,6 @@
 		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
 		"--hide HiddenTypedefConstant",
 		"--hide SuperfluousPrefix",
-		"--hide AnnotationExtraction",
-		// b/222738070
-		"--hide BannedThrow",
 	}
 
 	MetalavaAnnotationsWarningsFlags = strings.Join(metalavaAnnotationsWarningsFlags, " ")
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 1cfa642..38ed856 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -187,6 +187,33 @@
 	return apexInfo.ForPrebuiltApex
 }
 
+// For apex variant of modules, this returns true on the source variant if the prebuilt apex
+// has been selected using apex_contributions.
+// The prebuilt apex will be responsible for generating the dexpreopt rules of the deapexed java lib.
+func disableSourceApexVariant(ctx android.BaseModuleContext) bool {
+	if !isApexVariant(ctx) {
+		return false // platform variant
+	}
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+	psi := android.PrebuiltSelectionInfoMap{}
+	ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(am android.Module) {
+		psi, _ = android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider)
+	})
+	// Find the apex variant for this module
+	_, apexVariantsWithoutTestApexes, _ := android.ListSetDifference(apexInfo.InApexVariants, apexInfo.TestApexes)
+	disableSource := false
+	// find the selected apexes
+	for _, apexVariant := range apexVariantsWithoutTestApexes {
+		for _, selected := range psi.GetSelectedModulesForApiDomain(apexVariant) {
+			// If the apex_contribution for this api domain contains a prebuilt apex, disable the source variant
+			if strings.HasPrefix(selected, "prebuilt_com.google.android") {
+				disableSource = true
+			}
+		}
+	}
+	return disableSource
+}
+
 // Returns whether dexpreopt is applicable to the module.
 // When it returns true, neither profile nor dexpreopt artifacts will be generated.
 func (d *dexpreopter) dexpreoptDisabled(ctx android.BaseModuleContext, libName string) bool {
@@ -216,6 +243,10 @@
 		return true
 	}
 
+	if disableSourceApexVariant(ctx) {
+		return true
+	}
+
 	if _, isApex := android.ModuleProvider(ctx, android.ApexBundleInfoProvider); isApex {
 		// dexpreopt rules for system server jars can be generated in the ModuleCtx of prebuilt apexes
 		return false
diff --git a/java/droiddoc.go b/java/droiddoc.go
index df40d01..6a66f45 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -222,8 +222,6 @@
 	stubsSrcJar android.WritablePath
 
 	exportableStubsSrcJar android.WritablePath
-
-	runtimeStubsSrcJar android.WritablePath
 }
 
 func (j *Javadoc) OutputFiles(tag string) (android.Paths, error) {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index f4bcaca..76c8d88 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -227,7 +227,6 @@
 type annotationFlagsParams struct {
 	migratingNullability    bool
 	validatingNullability   bool
-	extractAnnotations      bool
 	nullabilityWarningsFile android.WritablePath
 	annotationsZip          android.WritablePath
 }
@@ -243,19 +242,16 @@
 	stubConfig              stubsCommandConfigParams
 }
 type stubsCommandConfigParams struct {
-	stubsType                   StubsType
-	javaVersion                 javaVersion
-	deps                        deps
-	checkApi                    bool
-	generateStubs               bool
-	doApiLint                   bool
-	doCheckReleased             bool
-	writeSdkValues              bool
-	migratingNullability        bool
-	validatingNullability       bool
-	annotationsEnabled          bool
-	apiLevelsAnnotationsEnabled bool
-	extractAnnotations          bool
+	stubsType             StubsType
+	javaVersion           javaVersion
+	deps                  deps
+	checkApi              bool
+	generateStubs         bool
+	doApiLint             bool
+	doCheckReleased       bool
+	writeSdkValues        bool
+	migratingNullability  bool
+	validatingNullability bool
 }
 
 // droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be
@@ -525,30 +521,30 @@
 }
 
 func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, params annotationFlagsParams) {
-	cmd.Flag(config.MetalavaAnnotationsFlags)
+	if Bool(d.properties.Annotations_enabled) {
+		cmd.Flag(config.MetalavaAnnotationsFlags)
 
-	if params.migratingNullability {
-		previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
-		cmd.FlagWithInput("--migrate-nullness ", previousApi)
-	}
+		if params.migratingNullability {
+			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
+			cmd.FlagWithInput("--migrate-nullness ", previousApi)
+		}
 
-	if s := String(d.properties.Validate_nullability_from_list); s != "" {
-		cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
-	}
+		if s := String(d.properties.Validate_nullability_from_list); s != "" {
+			cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
+		}
 
-	if params.validatingNullability {
-		cmd.FlagWithOutput("--nullability-warnings-txt ", params.nullabilityWarningsFile)
-	}
+		if params.validatingNullability {
+			cmd.FlagWithOutput("--nullability-warnings-txt ", params.nullabilityWarningsFile)
+		}
 
-	if params.extractAnnotations {
 		cmd.FlagWithOutput("--extract-annotations ", params.annotationsZip)
-	}
 
-	if len(d.properties.Merge_annotations_dirs) != 0 {
-		d.mergeAnnoDirFlags(ctx, cmd)
-	}
+		if len(d.properties.Merge_annotations_dirs) != 0 {
+			d.mergeAnnoDirFlags(ctx, cmd)
+		}
 
-	cmd.Flag(config.MetalavaAnnotationsWarningsFlags)
+		cmd.Flag(config.MetalavaAnnotationsWarningsFlags)
+	}
 }
 
 func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
@@ -573,11 +569,9 @@
 	})
 }
 
-func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, params stubsCommandParams) {
+func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsType StubsType, apiVersionsXml android.WritablePath) {
 	var apiVersions android.Path
-	stubsType := params.stubConfig.stubsType
-	apiVersionsXml := params.apiVersionsXml
-	if params.stubConfig.apiLevelsAnnotationsEnabled {
+	if proptools.Bool(d.properties.Api_levels_annotations_enabled) {
 		d.apiLevelsGenerationFlags(ctx, cmd, stubsType, apiVersionsXml)
 		apiVersions = apiVersionsXml
 	} else {
@@ -588,9 +582,7 @@
 				} else if stubsType == Exportable {
 					apiVersions = s.exportableArtifacts.apiVersionsXml
 				} else {
-					// if the stubs type does not generate api-versions.xml file, default to using the
-					// everything artifacts
-					apiVersions = s.everythingArtifacts.apiVersionsXml
+					ctx.ModuleErrorf("%s stubs type does not generate api-versions.xml file", stubsType.String())
 				}
 			} else {
 				ctx.PropertyErrorf("api_levels_module",
@@ -824,16 +816,13 @@
 	annotationParams := annotationFlagsParams{
 		migratingNullability:    params.stubConfig.migratingNullability,
 		validatingNullability:   params.stubConfig.validatingNullability,
-		extractAnnotations:      params.stubConfig.extractAnnotations,
 		nullabilityWarningsFile: params.nullabilityWarningsFile,
 		annotationsZip:          params.annotationsZip,
 	}
 
-	if params.stubConfig.annotationsEnabled {
-		d.annotationsFlags(ctx, cmd, annotationParams)
-	}
+	d.annotationsFlags(ctx, cmd, annotationParams)
 	d.inclusionAnnotationsFlags(ctx, cmd)
-	d.apiLevelsAnnotationsFlags(ctx, cmd, params)
+	d.apiLevelsAnnotationsFlags(ctx, cmd, params.stubConfig.stubsType, params.apiVersionsXml)
 
 	d.expandArgs(ctx, cmd)
 
@@ -863,13 +852,13 @@
 		d.everythingArtifacts.metadataZip = android.PathForModuleOut(ctx, Everything.String(), ctx.ModuleName()+"-metadata.zip")
 	}
 
-	if params.annotationsEnabled {
+	if Bool(d.properties.Annotations_enabled) {
 		if params.validatingNullability {
 			d.everythingArtifacts.nullabilityWarningsFile = android.PathForModuleOut(ctx, Everything.String(), ctx.ModuleName()+"_nullability_warnings.txt")
 		}
 		d.everythingArtifacts.annotationsZip = android.PathForModuleOut(ctx, Everything.String(), ctx.ModuleName()+"_annotations.zip")
 	}
-	if params.apiLevelsAnnotationsEnabled {
+	if Bool(d.properties.Api_levels_annotations_enabled) {
 		d.everythingArtifacts.apiVersionsXml = android.PathForModuleOut(ctx, Everything.String(), "api-versions.xml")
 	}
 
@@ -1047,7 +1036,7 @@
 		optionalCmdParams.metadataDir = d.exportableArtifacts.metadataDir
 	}
 
-	if params.annotationsEnabled {
+	if Bool(d.properties.Annotations_enabled) {
 		if params.validatingNullability {
 			d.exportableArtifacts.nullabilityWarningsFile = android.PathForModuleOut(ctx, params.stubsType.String(), ctx.ModuleName()+"_nullability_warnings.txt")
 			optionalCmdParams.nullabilityWarningsFile = d.exportableArtifacts.nullabilityWarningsFile
@@ -1055,7 +1044,7 @@
 		d.exportableArtifacts.annotationsZip = android.PathForModuleOut(ctx, params.stubsType.String(), ctx.ModuleName()+"_annotations.zip")
 		optionalCmdParams.annotationsZip = d.exportableArtifacts.annotationsZip
 	}
-	if params.apiLevelsAnnotationsEnabled {
+	if Bool(d.properties.Api_levels_annotations_enabled) {
 		d.exportableArtifacts.apiVersionsXml = android.PathForModuleOut(ctx, params.stubsType.String(), "api-versions.xml")
 		optionalCmdParams.apiVersionsXml = d.exportableArtifacts.apiVersionsXml
 	}
@@ -1073,38 +1062,6 @@
 	d.optionalStubCmd(ctx, optionalCmdParams)
 }
 
-// Sandbox rule for generating runtime stubs
-func (d *Droidstubs) runtimeStubCmd(ctx android.ModuleContext, params stubsCommandConfigParams) {
-
-	// We are only interested in generating the stubs srcjar,
-	// not other artifacts for the runtime stubs
-	params.checkApi = false
-	params.writeSdkValues = false
-	params.validatingNullability = false
-	params.extractAnnotations = false
-	params.apiLevelsAnnotationsEnabled = false
-
-	optionalCmdParams := stubsCommandParams{
-		stubConfig: params,
-	}
-
-	d.Javadoc.runtimeStubsSrcJar = android.PathForModuleOut(ctx, params.stubsType.String(), ctx.ModuleName()+"-"+"stubs.srcjar")
-	optionalCmdParams.stubsSrcJar = d.Javadoc.runtimeStubsSrcJar
-
-	// If aconfig_declarations property is not defined, all flagged apis symbols are stripped
-	// as no aconfig flags are enabled. In such case, the runtime stubs are identical to the
-	// exportable stubs, thus no additional metalava invocation is needed.
-	if len(d.properties.Aconfig_declarations) == 0 {
-		rule := android.NewRuleBuilder(pctx, ctx)
-		rule.Command().
-			Text("cp").Flag("-f").
-			Input(d.exportableStubsSrcJar).Output(d.runtimeStubsSrcJar)
-		rule.Build(fmt.Sprintf("metalava_%s", params.stubsType.String()), "metalava merged")
-	} else {
-		d.optionalStubCmd(ctx, optionalCmdParams)
-	}
-}
-
 func (d *Droidstubs) optionalStubCmd(ctx android.ModuleContext, params stubsCommandParams) {
 
 	params.srcJarDir = android.PathForModuleOut(ctx, params.stubConfig.stubsType.String(), "srcjars")
@@ -1176,8 +1133,6 @@
 
 	annotationsEnabled := Bool(d.properties.Annotations_enabled)
 
-	extractAnnotations := annotationsEnabled
-
 	migratingNullability := annotationsEnabled && String(d.properties.Previous_api) != ""
 	validatingNullability := annotationsEnabled && (strings.Contains(String(d.Javadoc.properties.Args), "--validate-nullability-from-merged-stubs") ||
 		String(d.properties.Validate_nullability_from_list) != "")
@@ -1185,40 +1140,27 @@
 	checkApi := apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
 		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released")
 
-	apiLevelsAnnotationsEnabled := proptools.Bool(d.properties.Api_levels_annotations_enabled)
-
 	stubCmdParams := stubsCommandConfigParams{
-		javaVersion:                 javaVersion,
-		deps:                        deps,
-		checkApi:                    checkApi,
-		generateStubs:               generateStubs,
-		doApiLint:                   doApiLint,
-		doCheckReleased:             doCheckReleased,
-		writeSdkValues:              writeSdkValues,
-		migratingNullability:        migratingNullability,
-		validatingNullability:       validatingNullability,
-		annotationsEnabled:          annotationsEnabled,
-		apiLevelsAnnotationsEnabled: apiLevelsAnnotationsEnabled,
-		extractAnnotations:          extractAnnotations,
+		javaVersion:           javaVersion,
+		deps:                  deps,
+		checkApi:              checkApi,
+		generateStubs:         generateStubs,
+		doApiLint:             doApiLint,
+		doCheckReleased:       doCheckReleased,
+		writeSdkValues:        writeSdkValues,
+		migratingNullability:  migratingNullability,
+		validatingNullability: validatingNullability,
 	}
 	stubCmdParams.stubsType = Everything
 	// Create default (i.e. "everything" stubs) rule for metalava
 	d.everythingStubCmd(ctx, stubCmdParams)
 
-	// The module generates "exportable" stubs regardless of whether
+	// The module generates "exportable" (and "runtime" eventually) stubs regardless of whether
 	// aconfig_declarations property is defined or not. If the property is not defined, the module simply
 	// strips all flagged apis to generate the "exportable" stubs
 	stubCmdParams.stubsType = Exportable
 	d.exportableStubCmd(ctx, stubCmdParams)
 
-	// "runtime" stubs do not generate any other artifacts than the stubs.
-	// Therefore, metalava does not have to run for "runtime" configuration
-	// when the module does not generate stubs.
-	if stubCmdParams.generateStubs {
-		stubCmdParams.stubsType = Runtime
-		d.runtimeStubCmd(ctx, stubCmdParams)
-	}
-
 	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
 
 		if len(d.Javadoc.properties.Out) > 0 {
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index c86e8bf..8da695f 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -397,47 +397,23 @@
 			"bar",
 		],
 	}
-	droidstubs {
-		name: "baz",
-		srcs: ["a/A.java"],
-		api_surface: "public",
-		check_api: {
-			current: {
-				api_file: "a/current.txt",
-				removed_api_file: "a/removed.txt",
-			}
-		},
-	}
 	`)
 
 	// Check that droidstubs depend on aconfig_declarations
 	android.AssertBoolEquals(t, "foo expected to depend on bar",
 		CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
 
-	fooModule := result.ModuleForTests("foo", "android_common")
+	m := result.ModuleForTests("foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
-		strings.Join(fooModule.AllOutputs(), ""), "revert-annotations-exportable.txt")
+		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
 
 	// revert-annotations.txt passed to exportable stubs generation metalava command
-	exportableManifest := fooModule.Output("metalava_exportable.sbox.textproto")
-	exportableCmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, exportableManifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", exportableCmdline, "revert-annotations-exportable.txt")
+	manifest := m.Output("metalava_exportable.sbox.textproto")
+	cmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, manifest).Commands[0].Command)
+	android.AssertStringDoesContain(t, "flagged api hide command not included", cmdline, "revert-annotations-exportable.txt")
 
 	android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
-		strings.Join(fooModule.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
-
-	// revert-annotations.txt passed to runtime stubs generation metalava command
-	runtimeManifest := fooModule.Output("metalava_runtime.sbox.textproto")
-	runtimeCmdline := String(android.RuleBuilderSboxProtoForTests(t, result.TestContext, runtimeManifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "flagged api hide command not included", runtimeCmdline, "revert-annotations-runtime.txt")
-
-	android.AssertStringDoesContain(t, "foo generates runtime stubs jar",
-		strings.Join(fooModule.AllOutputs(), ""), "runtime/foo-stubs.srcjar")
-
-	// If aconfig_declarations property is not defined, the runtime stubs is a copy of the exportable stubs
-	bazModule := result.ModuleForTests("baz", "android_common")
-	bazRuntimeCmdline := bazModule.Rule("metalava_runtime").RuleParams.Command
-	android.AssertStringDoesContain(t, "copy command should include the input stub", bazRuntimeCmdline, "exportable/baz-stubs.srcjar")
+		strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
 }
 
 func TestReleaseExportRuntimeApis(t *testing.T) {
diff --git a/mk2rbc/test/version_defaults.mk.test b/mk2rbc/test/version_defaults.mk.test
index 1666392..3ce60bc 100644
--- a/mk2rbc/test/version_defaults.mk.test
+++ b/mk2rbc/test/version_defaults.mk.test
@@ -3,8 +3,8 @@
   include $(INTERNAL_BUILD_ID_MAKEFILE)
 endif
 
-DEFAULT_PLATFORM_VERSION := TP1A
-.KATI_READONLY := DEFAULT_PLATFORM_VERSION
+RELEASE_PLATFORM_VERSION := TP1A
+.KATI_READONLY := RELEASE_PLATFORM_VERSION
 MIN_PLATFORM_VERSION := TP1A
 MAX_PLATFORM_VERSION := TP1A
 PLATFORM_VERSION_LAST_STABLE := 12
diff --git a/rust/config/lints.go b/rust/config/lints.go
index 7770af0..735aa16 100644
--- a/rust/config/lints.go
+++ b/rust/config/lints.go
@@ -53,6 +53,8 @@
 	// It should be assumed that any warning lint will be promoted to a
 	// deny.
 	defaultClippyLints = []string{
+		// Let people hack in peace. ;)
+		"-A clippy::disallowed_names",
 		"-A clippy::type-complexity",
 		"-A clippy::unnecessary_fallible_conversions",
 		"-A clippy::unnecessary-wraps",
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 07e01a1..2876bcb 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -19,6 +19,7 @@
 from __future__ import print_function
 
 import argparse
+import json
 import sys
 from xml.dom import minidom
 
@@ -31,6 +32,8 @@
 KNOWN_PREPARERS = ['com.android.tradefed.targetprep.TestAppInstallSetup',
                    'com.android.tradefed.targetprep.suite.SuiteApkInstaller']
 
+KNOWN_TEST_RUNNERS = ['com.android.tradefed.testtype.AndroidJUnitTest']
+
 MAINLINE_CONTROLLER = 'com.android.tradefed.testtype.suite.module.MainlineTestModuleController'
 
 def parse_args():
@@ -43,8 +46,12 @@
                       help=('overwrite package fields in the test config'))
   parser.add_argument('--test-file-name', default='', dest='test_file_name',
                       help=('overwrite test file name in the test config'))
+  parser.add_argument('--orig-test-file-name', default='', dest='orig_test_file_name',
+                      help=('Use with test-file-name to only override a single apk'))
   parser.add_argument('--mainline-package-name', default='', dest='mainline_package_name',
                       help=('overwrite mainline module package name in the test config'))
+  parser.add_argument('--test-runner-options', default='', dest='test_runner_options',
+                      help=('Add test runner options in the test config'))
   parser.add_argument('input', help='input test config file')
   parser.add_argument('output', help='output test config file')
   return parser.parse_args()
@@ -76,6 +83,18 @@
         if option.getAttribute('name') == "test-file-name":
           option.setAttribute('value', test_file_name)
 
+def overwrite_single_test_file_name(test_config_doc, orig_test_file_name, new_test_file_name):
+
+  test_config = parse_test_config(test_config_doc)
+  tests = get_children_with_tag(test_config, 'target_preparer')
+
+  for test in tests:
+    if test.getAttribute('class') in KNOWN_PREPARERS:
+      options = get_children_with_tag(test, 'option')
+      for option in options:
+        if option.getAttribute('name') == "test-file-name" and option.getAttribute('value') == orig_test_file_name:
+          option.setAttribute('value', new_test_file_name)
+
 def overwrite_mainline_module_package_name(test_config_doc, mainline_package_name):
 
   test_config = parse_test_config(test_config_doc)
@@ -86,6 +105,31 @@
         if option.getAttribute('name') == "mainline-module-package-name":
           option.setAttribute('value', mainline_package_name)
 
+def add_test_runner_options_toplevel(test_config_doc, test_runner_options):
+
+  test_config = parse_test_config(test_config_doc)
+
+  test_config.appendChild(test_config_doc.createComment("Options from Android.bp"))
+  test_config.appendChild(test_config_doc.createTextNode("\n"))
+  for new_option in json.loads(test_runner_options):
+    option = test_config_doc.createElement("option")
+    # name and value are mandatory,
+    name = new_option.get('Name')
+    if not name:
+      raise RuntimeError('"name" must set in test_runner_option"')
+    value = new_option.get('Value')
+    if not value:
+      raise RuntimeError('"value" must set in test_runner_option"')
+    option.setAttribute('name', name) # 'include-filter')
+    option.setAttribute('value', value) # 'android.test.example.devcodelab.DevCodelabTest#testHelloFail')
+    key = new_option.get('Key')
+    if key:
+      option.setAttribute('key', key) # 'include-filter')
+    # add tab and newline for readability
+    test_config.appendChild(test_config_doc.createTextNode("    "))
+    test_config.appendChild(option)
+    test_config.appendChild(test_config_doc.createTextNode("\n"))
+
 def main():
   """Program entry point."""
   try:
@@ -100,11 +144,20 @@
       overwrite_package_name(doc, manifest_doc, args.package_name)
 
     if args.test_file_name:
-      overwrite_test_file_name(doc, args.test_file_name)
+      if args.orig_test_file_name:
+        overwrite_single_test_file_name(doc, args.orig_test_file_name, args.test_file_name)
+      else:
+        # You probably never want to override the test_file_name if there
+        # are several in the xml, but this is currently only used on generated
+        # AndroidTest.xml where there is only a single test-file-name (no data)
+        overwrite_test_file_name(doc, args.test_file_name)
 
     if args.mainline_package_name:
       overwrite_mainline_module_package_name(doc, args.mainline_package_name)
 
+    if args.test_runner_options:
+      add_test_runner_options_toplevel(doc, args.test_runner_options)
+
     with open(args.output, 'w') as f:
       write_xml(f, doc)
 
diff --git a/tradefed/Android.bp b/tradefed/Android.bp
index f0336a3..e852584 100644
--- a/tradefed/Android.bp
+++ b/tradefed/Android.bp
@@ -13,6 +13,7 @@
         "autogen.go",
         "config.go",
         "makevars.go",
+        "providers.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/tradefed/providers.go b/tradefed/providers.go
new file mode 100644
index 0000000..f41e09e
--- /dev/null
+++ b/tradefed/providers.go
@@ -0,0 +1,21 @@
+package tradefed
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+// Output files we need from a base test that we derive from.
+type BaseTestProviderData struct {
+	// data files and apps for android_test
+	InstalledFiles android.Paths
+	// apk for android_test
+	OutputFile android.Path
+	// Either handwritten or generated TF xml.
+	TestConfig android.Path
+	// Other modules we require to be installed to run tests. We expect base to build them.
+	HostRequiredModuleNames []string
+}
+
+var BaseTestProviderKey = blueprint.NewProvider[BaseTestProviderData]()
diff --git a/tradefed_modules/Android.bp b/tradefed_modules/Android.bp
new file mode 100644
index 0000000..9969ae2
--- /dev/null
+++ b/tradefed_modules/Android.bp
@@ -0,0 +1,21 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-tradefed-modules",
+    pkgPath: "android/soong/tradefed_modules",
+    deps: [
+        "blueprint",
+        "soong-android",
+        "soong-java",
+        "soong-tradefed",
+    ],
+    srcs: [
+        "test_module_config.go",
+    ],
+    testSrcs: [
+        "test_module_config_test.go",
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
new file mode 100644
index 0000000..ba6ab94
--- /dev/null
+++ b/tradefed_modules/test_module_config.go
@@ -0,0 +1,219 @@
+package tradefed_modules
+
+import (
+	"android/soong/android"
+	"android/soong/tradefed"
+	"encoding/json"
+	"fmt"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	RegisterTestModuleConfigBuildComponents(android.InitRegistrationContext)
+}
+
+// Register the license_kind module type.
+func RegisterTestModuleConfigBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("test_module_config", TestModuleConfigFactory)
+}
+
+type testModuleConfigModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	base android.Module
+
+	tradefedProperties
+
+	// Our updated testConfig.
+	testConfig android.OutputPath
+	manifest   android.InstallPath
+	provider   tradefed.BaseTestProviderData
+}
+
+// Properties to list in Android.bp for this module.
+type tradefedProperties struct {
+	// Module name of the base test that we will run.
+	Base *string `android:"path,arch_variant"`
+
+	// Tradefed Options to add to tradefed xml when not one of the include or exclude filter or property.
+	// Sample: [{name: "TestRunnerOptionName", value: "OptionValue" }]
+	Options []tradefed.Option
+
+	// List of tradefed include annotations to add to tradefed xml, like "android.platform.test.annotations.Presubmit".
+	// Tests will be restricted to those matching an include_annotation or include_filter.
+	Include_annotations []string
+
+	// List of tradefed include annotations to add to tradefed xml, like "android.support.test.filters.FlakyTest".
+	// Tests matching an exclude annotation or filter will be skipped.
+	Exclude_annotations []string
+
+	// List of tradefed include filters to add to tradefed xml, like "fully.qualified.class#method".
+	// Tests will be restricted to those matching an include_annotation or include_filter.
+	Include_filters []string
+
+	// List of tradefed exclude filters to add to tradefed xml, like "fully.qualified.class#method".
+	// Tests matching an exclude annotation or filter will be skipped.
+	Exclude_filters []string
+
+	// List of compatibility suites (for example "cts", "vts") that the module should be
+	// installed into.
+	Test_suites []string
+}
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+}
+
+var (
+	testModuleConfigTag = dependencyTag{name: "TestModuleConfigBase"}
+	pctx                = android.NewPackageContext("android/soong/tradefed_modules")
+)
+
+func (m *testModuleConfigModule) InstallInTestcases() bool {
+	return true
+}
+
+func (m *testModuleConfigModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), testModuleConfigTag, *m.Base)
+}
+
+// Takes base's Tradefed Config xml file and generates a new one with the test properties
+// appeneded from this module.
+// Rewrite the name of the apk in "test-file-name" to be our module's name, rather than the original one.
+func (m *testModuleConfigModule) fixTestConfig(ctx android.ModuleContext, baseTestConfig android.Path) android.OutputPath {
+	// Test safe to do when no test_runner_options, but check for that earlier?
+	fixedConfig := android.PathForModuleOut(ctx, "test_config_fixer", ctx.ModuleName()+".config")
+	rule := android.NewRuleBuilder(pctx, ctx)
+	command := rule.Command().BuiltTool("test_config_fixer").Input(baseTestConfig).Output(fixedConfig)
+	options := m.composeOptions()
+	if len(options) == 0 {
+		ctx.ModuleErrorf("Test options must be given when using test_module_config. Set include/exclude filter or annotation.")
+	}
+	xmlTestModuleConfigSnippet, _ := json.Marshal(options)
+	escaped := proptools.NinjaAndShellEscape(string(xmlTestModuleConfigSnippet))
+	command.FlagWithArg("--test-file-name=", ctx.ModuleName()+".apk").
+		FlagWithArg("--orig-test-file-name=", *m.tradefedProperties.Base+".apk").
+		FlagWithArg("--test-runner-options=", escaped)
+	rule.Build("fix_test_config", "fix test config")
+	return fixedConfig.OutputPath
+}
+
+// Convert --exclude_filters: ["filter1", "filter2"] ->
+// [ Option{Name: "exclude-filters", Value: "filter1"}, Option{Name: "exclude-filters", Value: "filter2"},
+// ... + include + annotations ]
+func (m *testModuleConfigModule) composeOptions() []tradefed.Option {
+	options := m.Options
+	for _, e := range m.Exclude_filters {
+		options = append(options, tradefed.Option{Name: "exclude-filter", Value: e})
+	}
+	for _, i := range m.Include_filters {
+		options = append(options, tradefed.Option{Name: "include-filter", Value: i})
+	}
+	for _, e := range m.Exclude_annotations {
+		options = append(options, tradefed.Option{Name: "exclude-annotation", Value: e})
+	}
+	for _, i := range m.Include_annotations {
+		options = append(options, tradefed.Option{Name: "include-annotation", Value: i})
+	}
+	return options
+}
+
+// Files to write and where they come from:
+// 1) test_module_config.manifest
+//   - Leave a trail of where we got files from in case other tools need it.
+//
+// 2) $Module.config
+//   - comes from base's module.config (AndroidTest.xml), and then we add our test_options.
+//     provider.TestConfig
+//     [rules via soong_app_prebuilt]
+//
+// 3) $ARCH/$Module.apk
+//   - comes from base
+//     provider.OutputFile
+//     [rules via soong_app_prebuilt]
+//
+// 4) [bases data]
+//   - We copy all of bases data (like helper apks) to our install directory too.
+//     Since we call AndroidMkEntries on base, it will write out LOCAL_COMPATIBILITY_SUPPORT_FILES
+//     with this data and app_prebuilt.mk will generate the rules to copy it from base.
+//     We have no direct rules here to add to ninja.
+//
+// If we change to symlinks, this all needs to change.
+func (m *testModuleConfigModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+
+	ctx.VisitDirectDepsWithTag(testModuleConfigTag, func(dep android.Module) {
+		if provider, ok := android.OtherModuleProvider(ctx, dep, tradefed.BaseTestProviderKey); ok {
+			m.base = dep
+			m.provider = provider
+		} else {
+			ctx.ModuleErrorf("The base module '%s' does not provide test BaseTestProviderData.  Only 'android_test' modules are supported.", dep.Name())
+			return
+		}
+	})
+
+	// 1) A manifest file listing the base.
+	installDir := android.PathForModuleInstall(ctx, ctx.ModuleName())
+	out := android.PathForModuleOut(ctx, "test_module_config.manifest")
+	android.WriteFileRule(ctx, out, fmt.Sprintf("{%q: %q}", "base", *m.tradefedProperties.Base))
+	ctx.InstallFile(installDir, out.Base(), out)
+
+	// 2) Module.config / AndroidTest.xml
+	// Note, there is still a "test-tag" element with base's module name, but
+	// Tradefed team says its ignored anyway.
+	m.testConfig = m.fixTestConfig(ctx, m.provider.TestConfig)
+
+	// 3) Write ARCH/Module.apk in testcases.
+	// Handled by soong_app_prebuilt and OutputFile in entries.
+	// Nothing to do here.
+
+	// 4) Copy base's data files.
+	// Handled by soong_app_prebuilt and LOCAL_COMPATIBILITY_SUPPORT_FILES.
+	// Nothing to do here.
+}
+
+func TestModuleConfigFactory() android.Module {
+	module := &testModuleConfigModule{}
+
+	module.AddProperties(&module.tradefedProperties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+
+	return module
+}
+
+// Implements android.AndroidMkEntriesProvider
+var _ android.AndroidMkEntriesProvider = (*testModuleConfigModule)(nil)
+
+func (m *testModuleConfigModule) AndroidMkEntries() []android.AndroidMkEntries {
+	// We rely on base writing LOCAL_COMPATIBILITY_SUPPORT_FILES for its data files
+	entriesList := m.base.(android.AndroidMkEntriesProvider).AndroidMkEntries()
+	entries := &entriesList[0]
+	entries.OutputFile = android.OptionalPathForPath(m.provider.OutputFile)
+	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+		entries.SetString("LOCAL_MODULE", m.Name()) //  out module name, not base's
+
+		// Out update config file with extra options.
+		entries.SetPath("LOCAL_FULL_TEST_CONFIG", m.testConfig)
+		entries.SetString("LOCAL_MODULE_TAGS", "tests")
+		// Required for atest to run additional tradefed testtypes
+		entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", m.provider.HostRequiredModuleNames...)
+
+		// Clear the JNI symbols because they belong to base not us. Either transform the names in the string
+		// or clear the variable because we don't need it, we are copying bases libraries not generating
+		// new ones.
+		entries.SetString("LOCAL_SOONG_JNI_LIBS_SYMBOLS", "")
+
+		// Don't append to base's test-suites, only use the ones we define, so clear it before
+		// appending to it.
+		entries.SetString("LOCAL_COMPATIBILITY_SUITE", "")
+		if len(m.tradefedProperties.Test_suites) > 0 {
+			entries.AddCompatibilityTestSuites(m.tradefedProperties.Test_suites...)
+		} else {
+			entries.AddCompatibilityTestSuites("null-suite")
+		}
+	})
+	return entriesList
+}
diff --git a/tradefed_modules/test_module_config_test.go b/tradefed_modules/test_module_config_test.go
new file mode 100644
index 0000000..ff53043
--- /dev/null
+++ b/tradefed_modules/test_module_config_test.go
@@ -0,0 +1,204 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package tradefed_modules
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"strings"
+	"testing"
+)
+
+const bp = `
+		android_app {
+			name: "foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+
+                android_test_helper_app {
+                        name: "HelperApp",
+                        srcs: ["helper.java"],
+                }
+
+		android_test {
+			name: "base",
+			sdk_version: "current",
+                        data: [":HelperApp", "data/testfile"],
+		}
+
+                test_module_config {
+                        name: "derived_test",
+                        base: "base",
+                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                }
+
+`
+
+// Ensure we create files needed and set the AndroidMkEntries needed
+func TestModuleConfigAndroidTest(t *testing.T) {
+
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).RunTestWithBp(t, bp)
+
+	derived := ctx.ModuleForTests("derived_test", "android_common")
+	// Assert there are rules to create these files.
+	derived.Output("test_module_config.manifest")
+	derived.Output("test_config_fixer/derived_test.config")
+
+	// Ensure some basic rules exist.
+	ctx.ModuleForTests("base", "android_common").Output("package-res.apk")
+	entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
+
+	// Ensure some entries from base are there, specifically support files for data and helper apps.
+	assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
+
+	// And some new derived entries are there.
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE_TAGS"], []string{"tests"})
+
+	// And ones we override
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
+
+	android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "derived_test/android_common/test_config_fixer/derived_test.config")
+}
+
+// Make sure we call test-config-fixer with the right args.
+func TestModuleConfigOptions(t *testing.T) {
+
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).RunTestWithBp(t, bp)
+
+	// Check that we generate a rule to make a new AndroidTest.xml/Module.config file.
+	derived := ctx.ModuleForTests("derived_test", "android_common")
+	rule_cmd := derived.Rule("fix_test_config").RuleParams.Command
+	android.AssertStringDoesContain(t, "Bad FixConfig rule inputs", rule_cmd,
+		`--test-file-name=derived_test.apk --orig-test-file-name=base.apk --test-runner-options='[{"Name":"exclude-filter","Key":"","Value":"android.test.example.devcodelab.DevCodelabTest#testHelloFail"},{"Name":"include-annotation","Key":"","Value":"android.platform.test.annotations.LargeTest"}]'`)
+}
+
+// Ensure we error for a base we don't support.
+func TestModuleConfigBadBaseShouldFail(t *testing.T) {
+	badBp := `
+		java_test_host {
+			name: "base",
+                        srcs: ["a.java"],
+		}
+
+                test_module_config {
+                        name: "derived_test",
+                        base: "base",
+                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                }`
+
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).ExtendWithErrorHandler(
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern("does not provide test BaseTestProviderData")).
+		RunTestWithBp(t, badBp)
+
+	ctx.ModuleForTests("derived_test", "android_common")
+}
+
+// Ensure we error for a base we don't support.
+func TestModuleConfigNoFiltersOrAnnotationsShouldFail(t *testing.T) {
+	badBp := `
+		android_test {
+			name: "base",
+			sdk_version: "current",
+                        srcs: ["a.java"],
+		}
+
+                test_module_config {
+                        name: "derived_test",
+                        base: "base",
+                }`
+
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).ExtendWithErrorHandler(
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern("Test options must be given")).
+		RunTestWithBp(t, badBp)
+
+	ctx.ModuleForTests("derived_test", "android_common")
+}
+
+func TestModuleConfigMultipleDerivedTestsWriteDistinctMakeEntries(t *testing.T) {
+	multiBp := `
+		android_test {
+			name: "base",
+			sdk_version: "current",
+                        srcs: ["a.java"],
+		}
+
+                test_module_config {
+                        name: "derived_test",
+                        base: "base",
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                }
+
+                test_module_config {
+                        name: "another_derived_test",
+                        base: "base",
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                }`
+
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).RunTestWithBp(t, multiBp)
+
+	{
+		derived := ctx.ModuleForTests("derived_test", "android_common")
+		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
+		// All these should be the same in both derived tests
+		assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
+		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
+		// Except this one, which points to the updated tradefed xml file.
+		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "derived_test/android_common/test_config_fixer/derived_test.config")
+		// And this one, the module name.
+		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE"], []string{"derived_test"})
+	}
+
+	{
+		derived := ctx.ModuleForTests("another_derived_test", "android_common")
+		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
+		// All these should be the same in both derived tests
+		assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
+		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
+		// Except this one, which points to the updated tradefed xml file.
+		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "another_derived_test/android_common/test_config_fixer/another_derived_test.config")
+		// And this one, the module name.
+		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE"], []string{"another_derived_test"})
+	}
+}
+
+// Use for situations where the entries map contains pairs:  [srcPath:installedPath1, srcPath2:installedPath2]
+// and we want to compare the RHS of the pairs, i.e. installedPath1, installedPath2
+func assertEntryPairValues(t *testing.T, actual []string, expected []string) {
+	for i, e := range actual {
+		parts := strings.Split(e, ":")
+		if len(parts) != 2 {
+			t.Errorf("Expected entry to have a value delimited by :, received: %s", e)
+			return
+		}
+		android.AssertStringEquals(t, "", parts[1], expected[i])
+	}
+}
diff --git a/ui/build/config.go b/ui/build/config.go
index a380157..3143b6b 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -75,7 +75,6 @@
 	queryview                bool
 	reportMkMetrics          bool // Collect and report mk2bp migration progress metrics.
 	soongDocs                bool
-	multitreeBuild           bool // This is a multitree build.
 	skipConfig               bool
 	skipKati                 bool
 	skipKatiNinja            bool
@@ -423,10 +422,6 @@
 	// zip files produced by soong_zip.  Disable zipbomb detection.
 	ret.environ.Set("UNZIP_DISABLE_ZIPBOMB_DETECTION", "TRUE")
 
-	if ret.MultitreeBuild() {
-		ret.environ.Set("MULTITREE_BUILD", "true")
-	}
-
 	outDir := ret.OutDir()
 	buildDateTimeFile := filepath.Join(outDir, "build_date.txt")
 	if buildDateTime, ok := ret.environ.Get("BUILD_DATETIME"); ok && buildDateTime != "" {
@@ -788,8 +783,6 @@
 			c.skipMetricsUpload = true
 		} else if arg == "--mk-metrics" {
 			c.reportMkMetrics = true
-		} else if arg == "--multitree-build" {
-			c.multitreeBuild = true
 		} else if arg == "--search-api-dir" {
 			c.searchApiDir = true
 		} else if strings.HasPrefix(arg, "--ninja_weight_source=") {
@@ -1094,10 +1087,6 @@
 	return c.verbose
 }
 
-func (c *configImpl) MultitreeBuild() bool {
-	return c.multitreeBuild
-}
-
 func (c *configImpl) NinjaWeightListSource() NinjaWeightListSource {
 	return c.ninjaWeightListSource
 }
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index 5182b12..b1222fe 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -860,23 +860,24 @@
 }
 
 func TestGetConfigArgsBuildModulesInDirectory(t *testing.T) {
-	tests := []buildActionTestCase{{
-		description:  "normal execution in a directory",
-		dirsInTrees:  []string{"0/1/2"},
-		buildFiles:   []string{"0/1/2/Android.mk"},
-		args:         []string{"fake-module"},
-		curDir:       "0/1/2",
-		tidyOnly:     "",
-		expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"},
-	}, {
-		description:  "build file in parent directory",
-		dirsInTrees:  []string{"0/1/2"},
-		buildFiles:   []string{"0/1/Android.mk"},
-		args:         []string{},
-		curDir:       "0/1/2",
-		tidyOnly:     "",
-		expectedArgs: []string{"MODULES-IN-0-1"},
-	},
+	tests := []buildActionTestCase{
+		{
+			description:  "normal execution in a directory",
+			dirsInTrees:  []string{"0/1/2"},
+			buildFiles:   []string{"0/1/2/Android.mk"},
+			args:         []string{"fake-module"},
+			curDir:       "0/1/2",
+			tidyOnly:     "",
+			expectedArgs: []string{"fake-module", "MODULES-IN-0-1-2"},
+		}, {
+			description:  "build file in parent directory",
+			dirsInTrees:  []string{"0/1/2"},
+			buildFiles:   []string{"0/1/Android.mk"},
+			args:         []string{},
+			curDir:       "0/1/2",
+			tidyOnly:     "",
+			expectedArgs: []string{"MODULES-IN-0-1"},
+		},
 		{
 			description:  "build file in parent directory, multiple module names passed in",
 			dirsInTrees:  []string{"0/1/2"},
@@ -903,15 +904,6 @@
 			tidyOnly:     "",
 			expectedArgs: []string{},
 		}, {
-			description:  "multitree build action executed at root directory",
-			dirsInTrees:  []string{},
-			buildFiles:   []string{},
-			rootSymlink:  false,
-			args:         []string{"--multitree-build"},
-			curDir:       ".",
-			tidyOnly:     "",
-			expectedArgs: []string{"--multitree-build"},
-		}, {
 			description:  "build action executed at root directory in symlink",
 			dirsInTrees:  []string{},
 			buildFiles:   []string{},
@@ -953,7 +945,8 @@
 			curDir:       "",
 			tidyOnly:     "",
 			expectedArgs: []string{"-j", "-k", "fake_module"},
-		}}
+		},
+	}
 	for _, tt := range tests {
 		t.Run("build action BUILD_MODULES_IN_DIR, "+tt.description, func(t *testing.T) {
 			testGetConfigArgs(t, tt, BUILD_MODULES_IN_A_DIRECTORY)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index a201ac5..79584c6 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -197,9 +197,6 @@
 		commonArgs = append(commonArgs, "-t")
 	}
 
-	if pb.config.multitreeBuild {
-		commonArgs = append(commonArgs, "--multitree-build")
-	}
 	if pb.config.buildFromSourceStub {
 		commonArgs = append(commonArgs, "--build-from-source-stub")
 	}
@@ -305,9 +302,6 @@
 	if config.EmptyNinjaFile() {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 	}
-	if config.MultitreeBuild() {
-		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--multitree-build")
-	}
 	if config.buildFromSourceStub {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--build-from-source-stub")
 	}