Merge "Handles if a space is encountered while parsing the rule" into main
diff --git a/README.md b/README.md
index f471c47..93260e6 100644
--- a/README.md
+++ b/README.md
@@ -314,6 +314,9 @@
 * `["//visibility:override"]`: Discards any rules inherited from defaults or a
 creating module. Can only be used at the beginning of a list of visibility
 rules.
+* `["//visibility:any_partition"]`: Any modules of type android_filesystem
+or android_system_image can use this module. Intended for modules that no one
+should link against, but should still be included in soong-built partitions.
 * `["//some/package:__pkg__", "//other/package:__pkg__"]`: Only modules in
 `some/package` and `other/package` (defined in `some/package/*.bp` and
 `other/package/*.bp`) have access to this module. Note that sub-packages do not
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index 78f506a..392e819 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -40,6 +40,9 @@
 
 		// Container(system/vendor/apex) that this module belongs to
 		Container string
+
+		// The flags will only be repackaged if this prop is true.
+		Exportable bool
 	}
 
 	intermediatePath android.WritablePath
@@ -159,6 +162,7 @@
 	android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, android.AconfigDeclarationsProviderData{
 		Package:                     module.properties.Package,
 		Container:                   module.properties.Container,
+		Exportable:                  module.properties.Exportable,
 		IntermediateCacheOutputPath: intermediateCacheFilePath,
 		IntermediateDumpOutputPath:  intermediateDumpFilePath,
 	})
diff --git a/aconfig/aconfig_declarations_test.go b/aconfig/aconfig_declarations_test.go
index d508af7..1fe3c86 100644
--- a/aconfig/aconfig_declarations_test.go
+++ b/aconfig/aconfig_declarations_test.go
@@ -27,6 +27,7 @@
 			name: "module_name",
 			package: "com.example.package",
 			container: "com.android.foo",
+			exportable: true,
 			srcs: [
 				"foo.aconfig",
 				"bar.aconfig",
@@ -41,6 +42,7 @@
 	depData, _ := android.SingletonModuleProvider(result, module, android.AconfigDeclarationsProviderKey)
 	android.AssertStringEquals(t, "package", depData.Package, "com.example.package")
 	android.AssertStringEquals(t, "container", depData.Container, "com.android.foo")
+	android.AssertBoolEquals(t, "exportable", depData.Exportable, true)
 	if !strings.HasSuffix(depData.IntermediateCacheOutputPath.String(), "/intermediate.pb") {
 		t.Errorf("Missing intermediates proto path in provider: %s", depData.IntermediateCacheOutputPath.String())
 	}
@@ -48,3 +50,22 @@
 		t.Errorf("Missing intermediates text path in provider: %s", depData.IntermediateDumpOutputPath.String())
 	}
 }
+
+func TestAconfigDeclarationsWithExportableUnset(t *testing.T) {
+	bp := `
+		aconfig_declarations {
+			name: "module_name",
+			package: "com.example.package",
+			container: "com.android.foo",
+			srcs: [
+				"foo.aconfig",
+				"bar.aconfig",
+			],
+		}
+	`
+	result := runTest(t, android.FixtureExpectsNoErrors, bp)
+
+	module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+	depData, _ := android.SingletonModuleProvider(result, module, android.AconfigDeclarationsProviderKey)
+	android.AssertBoolEquals(t, "exportable", depData.Exportable, false)
+}
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index b6c9023..72fe495 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -31,7 +31,8 @@
 }
 
 type allAconfigDeclarationsSingleton struct {
-	intermediatePath android.OutputPath
+	intermediateBinaryProtoPath android.OutputPath
+	intermediateTextProtoPath   android.OutputPath
 }
 
 func (this *allAconfigDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
@@ -59,20 +60,37 @@
 		panic(fmt.Errorf("Only one aconfig_declarations allowed for each package."))
 	}
 
-	// Generate build action for aconfig
-	this.intermediatePath = android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
+	// Generate build action for aconfig (binary proto output)
+	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        AllDeclarationsRule,
 		Inputs:      cacheFiles,
-		Output:      this.intermediatePath,
+		Output:      this.intermediateBinaryProtoPath,
 		Description: "all_aconfig_declarations",
 		Args: map[string]string{
 			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
 		},
 	})
-	ctx.Phony("all_aconfig_declarations", this.intermediatePath)
+	ctx.Phony("all_aconfig_declarations", this.intermediateBinaryProtoPath)
+
+	// Generate build action for aconfig (text proto output)
+	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        AllDeclarationsRuleTextProto,
+		Inputs:      cacheFiles,
+		Output:      this.intermediateTextProtoPath,
+		Description: "all_aconfig_declarations_textproto",
+		Args: map[string]string{
+			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
+		},
+	})
+	ctx.Phony("all_aconfig_declarations_textproto", this.intermediateTextProtoPath)
 }
 
 func (this *allAconfigDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("droid", this.intermediatePath)
+	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
+	for _, goal := range []string{"droid", "sdk"} {
+		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "flags.pb")
+		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "flags.textproto")
+	}
 }
diff --git a/aconfig/codegen/Android.bp b/aconfig/codegen/Android.bp
index 494f7e6..0c78b94 100644
--- a/aconfig/codegen/Android.bp
+++ b/aconfig/codegen/Android.bp
@@ -17,6 +17,7 @@
         "soong-rust",
     ],
     srcs: [
+        "aconfig_declarations_group.go",
         "cc_aconfig_library.go",
         "init.go",
         "java_aconfig_library.go",
@@ -24,6 +25,7 @@
         "testing.go",
     ],
     testSrcs: [
+        "aconfig_declarations_group_test.go",
         "java_aconfig_library_test.go",
         "cc_aconfig_library_test.go",
         "rust_aconfig_library_test.go",
diff --git a/aconfig/codegen/aconfig_declarations_group.go b/aconfig/codegen/aconfig_declarations_group.go
new file mode 100644
index 0000000..1c91bee
--- /dev/null
+++ b/aconfig/codegen/aconfig_declarations_group.go
@@ -0,0 +1,139 @@
+// 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 codegen
+
+import (
+	"fmt"
+	"maps"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+type dependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+}
+
+var (
+	aconfigDeclarationsGroupTag = dependencyTag{name: "aconfigDeclarationsGroup"}
+	javaAconfigLibraryTag       = dependencyTag{name: "javaAconfigLibrary"}
+	ccAconfigLibraryTag         = dependencyTag{name: "ccAconfigLibrary"}
+	rustAconfigLibraryTag       = dependencyTag{name: "rustAconfigLibrary"}
+)
+
+type AconfigDeclarationsGroup struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	properties AconfigDeclarationsGroupProperties
+
+	aconfigDeclarationNames      []string
+	intermediateCacheOutputPaths android.Paths
+	javaSrcjars                  android.Paths
+	modeInfos                    map[string]android.ModeInfo
+}
+
+type AconfigDeclarationsGroupProperties struct {
+
+	// Name of the aconfig_declarations_group modules
+	Aconfig_declarations_groups []string
+
+	// Name of the java_aconfig_library modules
+	Java_aconfig_libraries []string
+
+	// Name of the cc_aconfig_library modules
+	Cc_aconfig_libraries []string
+
+	// Name of the rust_aconfig_library modules
+	Rust_aconfig_libraries []string
+}
+
+func AconfigDeclarationsGroupFactory() android.Module {
+	module := &AconfigDeclarationsGroup{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+func (adg *AconfigDeclarationsGroup) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), aconfigDeclarationsGroupTag, adg.properties.Aconfig_declarations_groups...)
+	ctx.AddDependency(ctx.Module(), javaAconfigLibraryTag, adg.properties.Java_aconfig_libraries...)
+	ctx.AddDependency(ctx.Module(), ccAconfigLibraryTag, adg.properties.Cc_aconfig_libraries...)
+	ctx.AddDependency(ctx.Module(), rustAconfigLibraryTag, adg.properties.Rust_aconfig_libraries...)
+}
+
+func (adg *AconfigDeclarationsGroup) VisitDeps(ctx android.ModuleContext) {
+	adg.modeInfos = make(map[string]android.ModeInfo)
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		if provider, ok := android.OtherModuleProvider(ctx, dep, android.CodegenInfoProvider); ok {
+
+			// aconfig declaration names and cache files are collected for all aconfig library dependencies
+			adg.aconfigDeclarationNames = append(adg.aconfigDeclarationNames, provider.AconfigDeclarations...)
+			adg.intermediateCacheOutputPaths = append(adg.intermediateCacheOutputPaths, provider.IntermediateCacheOutputPaths...)
+
+			switch tag {
+			case aconfigDeclarationsGroupTag:
+				// Will retrieve outputs from another language codegen modules when support is added
+				adg.javaSrcjars = append(adg.javaSrcjars, provider.Srcjars...)
+				maps.Copy(adg.modeInfos, provider.ModeInfos)
+			case javaAconfigLibraryTag:
+				adg.javaSrcjars = append(adg.javaSrcjars, provider.Srcjars...)
+				maps.Copy(adg.modeInfos, provider.ModeInfos)
+			case ccAconfigLibraryTag:
+				maps.Copy(adg.modeInfos, provider.ModeInfos)
+			case rustAconfigLibraryTag:
+				maps.Copy(adg.modeInfos, provider.ModeInfos)
+			}
+		}
+	})
+}
+
+func (adg *AconfigDeclarationsGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	adg.VisitDeps(ctx)
+	adg.aconfigDeclarationNames = android.FirstUniqueStrings(adg.aconfigDeclarationNames)
+	adg.intermediateCacheOutputPaths = android.FirstUniquePaths(adg.intermediateCacheOutputPaths)
+
+	android.SetProvider(ctx, android.CodegenInfoProvider, android.CodegenInfo{
+		AconfigDeclarations:          adg.aconfigDeclarationNames,
+		IntermediateCacheOutputPaths: adg.intermediateCacheOutputPaths,
+		Srcjars:                      adg.javaSrcjars,
+		ModeInfos:                    adg.modeInfos,
+	})
+}
+
+var _ android.OutputFileProducer = (*AconfigDeclarationsGroup)(nil)
+
+func (adg *AconfigDeclarationsGroup) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return adg.intermediateCacheOutputPaths, nil
+	case ".srcjars":
+		return adg.javaSrcjars, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %s", tag)
+	}
+}
+
+func (adg *AconfigDeclarationsGroup) Srcjars() android.Paths {
+	return adg.javaSrcjars
+}
+
+func (adg *AconfigDeclarationsGroup) AconfigDeclarations() []string {
+	return adg.aconfigDeclarationNames
+}
diff --git a/aconfig/codegen/aconfig_declarations_group_test.go b/aconfig/codegen/aconfig_declarations_group_test.go
new file mode 100644
index 0000000..ec7cea3
--- /dev/null
+++ b/aconfig/codegen/aconfig_declarations_group_test.go
@@ -0,0 +1,79 @@
+// 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 codegen
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"testing"
+)
+
+func TestAconfigDeclarationsGroup(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithAconfigBuildComponents,
+		java.PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		aconfig_declarations {
+			name: "foo-aconfig",
+			package: "com.example.package",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "foo-java",
+			aconfig_declarations: "foo-aconfig",
+		}
+
+		aconfig_declarations {
+			name: "bar-aconfig",
+			package: "com.example.package",
+			srcs: ["foo.aconfig"],
+		}
+
+		java_aconfig_library {
+			name: "bar-java",
+			aconfig_declarations: "bar-aconfig",
+		}
+
+		aconfig_declarations_group {
+			name: "my_group",
+			java_aconfig_libraries: [
+				"foo-java",
+				"bar-java",
+			],
+		}
+
+		java_library {
+			name: "baz",
+			srcs: [
+				":my_group{.srcjars}",
+			],
+		}
+	`)
+
+	// Check if aconfig_declarations_group module depends on the aconfig_library modules
+	java.CheckModuleDependencies(t, result.TestContext, "my_group", "", []string{
+		`bar-java`,
+		`foo-java`,
+	})
+
+	// Check if srcjar files are correctly passed to the reverse dependency of
+	// aconfig_declarations_group module
+	bazModule := result.ModuleForTests("baz", "android_common")
+	bazJavacSrcjars := bazModule.Rule("javac").Args["srcJars"]
+	errorMessage := "baz javac argument expected to contain srcjar provided by aconfig_declrations_group"
+	android.AssertStringDoesContain(t, errorMessage, bazJavacSrcjars, "foo-java.srcjar")
+	android.AssertStringDoesContain(t, errorMessage, bazJavacSrcjars, "bar-java.srcjar")
+}
diff --git a/aconfig/codegen/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
index 50cd4de..80e4926 100644
--- a/aconfig/codegen/cc_aconfig_library.go
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -146,4 +146,12 @@
 			"mode":   mode,
 		},
 	})
+
+	android.SetProvider(ctx, android.CodegenInfoProvider, android.CodegenInfo{
+		ModeInfos: map[string]android.ModeInfo{
+			ctx.ModuleName(): {
+				Container: declarations.Container,
+				Mode:      mode,
+			}},
+	})
 }
diff --git a/aconfig/codegen/init.go b/aconfig/codegen/init.go
index 0bff9d2..73a8951 100644
--- a/aconfig/codegen/init.go
+++ b/aconfig/codegen/init.go
@@ -77,6 +77,7 @@
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("aconfig_declarations_group", AconfigDeclarationsGroupFactory)
 	ctx.RegisterModuleType("cc_aconfig_library", CcAconfigLibraryFactory)
 	ctx.RegisterModuleType("java_aconfig_library", JavaDeclarationsLibraryFactory)
 	ctx.RegisterModuleType("rust_aconfig_library", RustAconfigLibraryFactory)
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
index 4b8d346..1378dfe 100644
--- a/aconfig/codegen/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -92,6 +92,12 @@
 		ctx.PropertyErrorf("mode", "%q is not a supported mode", mode)
 	}
 
+	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,
 		Input:       declarations.IntermediateCacheOutputPath,
@@ -102,12 +108,26 @@
 		},
 	})
 
-	// Mark our generated code as possibly needing jarjar repackaging
-	// TODO: Maybe control this with a property?
-	module.AddJarJarRenameRule(declarations.Package+".Flags", "")
-	module.AddJarJarRenameRule(declarations.Package+".FeatureFlags", "")
-	module.AddJarJarRenameRule(declarations.Package+".FeatureFlagsImpl", "")
-	module.AddJarJarRenameRule(declarations.Package+".FakeFeatureFlagsImpl", "")
+	if declarations.Exportable {
+		// Mark our generated code as possibly needing jarjar repackaging
+		// The repackaging only happens when the corresponding aconfig_declaration
+		// has property exportable true
+		module.AddJarJarRenameRule(declarations.Package+".Flags", "")
+		module.AddJarJarRenameRule(declarations.Package+".FeatureFlags", "")
+		module.AddJarJarRenameRule(declarations.Package+".FeatureFlagsImpl", "")
+		module.AddJarJarRenameRule(declarations.Package+".FakeFeatureFlagsImpl", "")
+	}
+
+	android.SetProvider(ctx, android.CodegenInfoProvider, android.CodegenInfo{
+		AconfigDeclarations:          []string{declarationsModules[0].Name()},
+		IntermediateCacheOutputPaths: android.Paths{declarations.IntermediateCacheOutputPath},
+		Srcjars:                      android.Paths{srcJarPath},
+		ModeInfos: map[string]android.ModeInfo{
+			ctx.ModuleName(): {
+				Container: declarations.Container,
+				Mode:      mode,
+			}},
+	})
 
 	return srcJarPath
 }
diff --git a/aconfig/codegen/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
index 85d2675..de45b5c 100644
--- a/aconfig/codegen/java_aconfig_library_test.go
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -176,6 +176,7 @@
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
 				srcs: ["foo.aconfig"],
+				exportable: true,
 			}
 
 			java_aconfig_library {
diff --git a/aconfig/codegen/rust_aconfig_library.go b/aconfig/codegen/rust_aconfig_library.go
index 2ab54b6..3f7495b 100644
--- a/aconfig/codegen/rust_aconfig_library.go
+++ b/aconfig/codegen/rust_aconfig_library.go
@@ -85,6 +85,15 @@
 		},
 	})
 	a.BaseSourceProvider.OutputFiles = android.Paths{generatedSource}
+
+	android.SetProvider(ctx, android.CodegenInfoProvider, android.CodegenInfo{
+		ModeInfos: map[string]android.ModeInfo{
+			ctx.ModuleName(): {
+				Container: declarations.Container,
+				Mode:      mode,
+			}},
+	})
+
 	return generatedSource
 }
 
diff --git a/aconfig/init.go b/aconfig/init.go
index 3e9d297..4625128 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -43,7 +43,7 @@
 	// For create-device-config-sysprops: Generate aconfig flag value map text file
 	aconfigTextRule = pctx.AndroidStaticRule("aconfig_text",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --format='{fully_qualified_name}={state:bool}'` +
+			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}'` +
 				` --cache ${in}` +
 				` --out ${out}.tmp` +
 				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
@@ -56,11 +56,26 @@
 	// For all_aconfig_declarations: Combine all parsed_flags proto files
 	AllDeclarationsRule = pctx.AndroidStaticRule("All_aconfig_declarations_dump",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --format protobuf --out ${out} ${cache_files}`,
+			Command: `${aconfig} dump-cache --dedup --format protobuf --out ${out} ${cache_files}`,
 			CommandDeps: []string{
 				"${aconfig}",
 			},
 		}, "cache_files")
+	AllDeclarationsRuleTextProto = pctx.AndroidStaticRule("All_aconfig_declarations_dump_textproto",
+		blueprint.RuleParams{
+			Command: `${aconfig} dump-cache --dedup --format textproto --out ${out} ${cache_files}`,
+			CommandDeps: []string{
+				"${aconfig}",
+			},
+		}, "cache_files")
+
+	CreateStorageRule = pctx.AndroidStaticRule("aconfig_create_storage",
+		blueprint.RuleParams{
+			Command: `${aconfig} create-storage --container ${container} --file ${file_type} --out ${out} ${cache_files}`,
+			CommandDeps: []string{
+				"${aconfig}",
+			},
+		}, "container", "file_type", "cache_files")
 
 	// For exported_java_aconfig_library: Generate a JAR from all
 	// java_aconfig_libraries to be consumed by apps built outside the
@@ -73,7 +88,7 @@
 		blueprint.RuleParams{
 			Command: `rm -rf ${out}.tmp` +
 				`&& for cache in ${cache_files}; do ` +
-				`  if [ -n "$$(${aconfig} dump-cache --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` +
+				`  if [ -n "$$(${aconfig} dump-cache --dedup --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` +
 				`    ${aconfig} create-java-lib --cache $$cache --mode=exported --out ${out}.tmp; ` +
 				`  fi ` +
 				`done` +
diff --git a/android/Android.bp b/android/Android.bp
index e73f355..03619f4 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -16,7 +16,6 @@
         "soong-remoteexec",
         "soong-response",
         "soong-shared",
-        "soong-starlark",
         "soong-starlark-format",
         "soong-ui-metrics_proto",
         "soong-android-allowlists",
@@ -135,6 +134,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/aconfig_providers.go b/android/aconfig_providers.go
index 1444e7d..fcc57e1 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"io"
+	"maps"
 	"reflect"
 
 	"github.com/google/blueprint"
@@ -35,6 +36,7 @@
 type AconfigDeclarationsProviderData struct {
 	Package                     string
 	Container                   string
+	Exportable                  bool
 	IntermediateCacheOutputPath WritablePath
 	IntermediateDumpOutputPath  WritablePath
 }
@@ -49,6 +51,35 @@
 
 var AconfigTransitiveDeclarationsInfoProvider = blueprint.NewProvider[AconfigTransitiveDeclarationsInfo]()
 
+type ModeInfo struct {
+	Container string
+	Mode      string
+}
+type CodegenInfo struct {
+	// AconfigDeclarations is the name of the aconfig_declarations modules that
+	// the codegen module is associated with
+	AconfigDeclarations []string
+
+	// Paths to the cache files of the associated aconfig_declaration modules
+	IntermediateCacheOutputPaths Paths
+
+	// Paths to the srcjar files generated from the java_aconfig_library modules
+	Srcjars Paths
+
+	ModeInfos map[string]ModeInfo
+}
+
+var CodegenInfoProvider = blueprint.NewProvider[CodegenInfo]()
+
+func propagateModeInfos(ctx ModuleContext, module Module, to, from map[string]ModeInfo) {
+	if len(from) > 0 {
+		depTag := ctx.OtherModuleDependencyTag(module)
+		if tag, ok := depTag.(PropagateAconfigValidationDependencyTag); ok && tag.PropagateAconfigValidation() {
+			maps.Copy(to, from)
+		}
+	}
+}
+
 // CollectDependencyAconfigFiles is used by some module types to provide finer dependency graphing than
 // we can do in ModuleBase.
 func CollectDependencyAconfigFiles(ctx ModuleContext, mergedAconfigFiles *map[string]Paths) {
@@ -89,13 +120,40 @@
 
 type aconfigPropagatingDeclarationsInfo struct {
 	AconfigFiles map[string]Paths
+	ModeInfos    map[string]ModeInfo
 }
 
 var aconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
 
+func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) {
+	if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
+		for k, v := range dep.ModeInfos {
+			msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n",
+				module.Name(), container, k, v.Container, v.Mode)
+			if v.Container != container && v.Mode != "exported" && v.Mode != "force-read-only" {
+				if asError {
+					ctx.ModuleErrorf(msg)
+				} else {
+					fmt.Printf("WARNING: " + msg)
+				}
+			} else {
+				if !asError {
+					fmt.Printf("PASSED: " + msg)
+				}
+			}
+		}
+	}
+}
+
 func aconfigUpdateAndroidBuildActions(ctx ModuleContext) {
 	mergedAconfigFiles := make(map[string]Paths)
+	mergedModeInfos := make(map[string]ModeInfo)
+
 	ctx.VisitDirectDepsIgnoreBlueprint(func(module Module) {
+		if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
+			maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
+		}
+
 		// If any of our dependencies have aconfig declarations (directly or propagated), then merge those and provide them.
 		if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
 			mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
@@ -104,6 +162,7 @@
 			for container, v := range dep.AconfigFiles {
 				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
 			}
+			propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos)
 		}
 		if dep, ok := OtherModuleProvider(ctx, module, AconfigTransitiveDeclarationsInfoProvider); ok {
 			for container, v := range dep.AconfigFiles {
@@ -119,6 +178,7 @@
 
 		SetProvider(ctx, aconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
 			AconfigFiles: mergedAconfigFiles,
+			ModeInfos:    mergedModeInfos,
 		})
 	}
 }
diff --git a/android/all_teams.go b/android/all_teams.go
index dd7d2db..b177e20 100644
--- a/android/all_teams.go
+++ b/android/all_teams.go
@@ -68,10 +68,6 @@
 	this.teams_for_mods = make(map[string]moduleTeamInfo)
 
 	ctx.VisitAllModules(func(module Module) {
-		if !module.Enabled() {
-			return
-		}
-
 		bpFile := ctx.BlueprintFile(module)
 
 		// Package Modules and Team Modules are stored in a map so we can look them up by name for
diff --git a/android/apex.go b/android/apex.go
index 4d36a93..8759905 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -976,3 +976,18 @@
 	// Map from the apex library name (without prebuilt_ prefix) to the dex file path on host
 	LibraryNameToDexJarPathOnHost map[string]Path
 }
+
+var PrebuiltInfoProvider = blueprint.NewProvider[PrebuiltInfo]()
+
+// contents of prebuilt_info.json
+type PrebuiltInfo struct {
+	// Name of the apex, without the prebuilt_ prefix
+	Name string
+
+	Is_prebuilt bool
+
+	// This is relative to root of the workspace.
+	// In case of mainline modules, this file contains the build_id that was used
+	// to generate the mainline module prebuilt.
+	Prebuilt_info_file_path string `json:",omitempty"`
+}
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index 236abf6..c76d9c2 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"strings"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -25,11 +27,13 @@
 
 func RegisterApexContributionsBuildComponents(ctx RegistrationContext) {
 	ctx.RegisterModuleType("apex_contributions", apexContributionsFactory)
+	ctx.RegisterModuleType("apex_contributions_defaults", apexContributionsDefaultsFactory)
 	ctx.RegisterSingletonModuleType("all_apex_contributions", allApexContributionsFactory)
 }
 
 type apexContributions struct {
 	ModuleBase
+	DefaultableModuleBase
 	properties contributionProps
 }
 
@@ -59,6 +63,7 @@
 	module := &apexContributions{}
 	module.AddProperties(&module.properties)
 	InitAndroidModule(module)
+	InitDefaultableModule(module)
 	return module
 }
 
@@ -68,6 +73,18 @@
 func (m *apexContributions) GenerateAndroidBuildActions(ctx ModuleContext) {
 }
 
+type apexContributionsDefaults struct {
+	ModuleBase
+	DefaultsModuleBase
+}
+
+func apexContributionsDefaultsFactory() Module {
+	module := &apexContributionsDefaults{}
+	module.AddProperties(&contributionProps{})
+	InitDefaultsModule(module)
+	return module
+}
+
 // A container for apex_contributions.
 // Based on product_config, it will create a dependency on the selected
 // apex_contributions per mainline module
@@ -98,6 +115,19 @@
 func (a *allApexContributions) SetPrebuiltSelectionInfoProvider(ctx BaseModuleContext) {
 	addContentsToProvider := func(p *PrebuiltSelectionInfoMap, m *apexContributions) {
 		for _, content := range m.Contents() {
+			// Skip any apexes that have been added to the product specific ignore list
+			if InList(content, ctx.Config().BuildIgnoreApexContributionContents()) {
+				continue
+			}
+			// Coverage builds for TARGET_RELEASE=foo should always build from source,
+			// even if TARGET_RELEASE=foo uses prebuilt mainline modules.
+			// This is necessary because the checked-in prebuilts were generated with
+			// instrumentation turned off.
+			//
+			// Skip any prebuilt contents in coverage builds
+			if strings.HasPrefix(content, "prebuilt_") && (ctx.Config().JavaCoverageEnabled() || ctx.DeviceConfig().NativeCoverageEnabled()) {
+				continue
+			}
 			if !ctx.OtherModuleExists(content) && !ctx.Config().AllowMissingDependencies() {
 				ctx.ModuleErrorf("%s listed in apex_contributions %s does not exist\n", content, m.Name())
 			}
diff --git a/android/api_levels.go b/android/api_levels.go
index c019f99..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"
@@ -288,6 +287,8 @@
 // a core-for-system-modules.jar for the module-lib API scope.
 var LastWithoutModuleLibCoreSystemModules = uncheckedFinalApiLevel(31)
 
+var ApiLevelR = uncheckedFinalApiLevel(30)
+
 // ReplaceFinalizedCodenames returns the API level number associated with that API level
 // if the `raw` input is the codename of an API level has been finalized.
 // If the input is *not* a finalized codename, the input is returned unmodified.
@@ -438,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/base_module_context.go b/android/base_module_context.go
index b9c1153..dd38a4e 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -16,9 +16,11 @@
 
 import (
 	"fmt"
-	"github.com/google/blueprint"
 	"regexp"
 	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
 )
 
 // BaseModuleContext is the same as blueprint.BaseModuleContext except that Config() returns
@@ -214,6 +216,10 @@
 	// getMissingDependencies returns the list of missing dependencies.
 	// Calling this function prevents adding new dependencies.
 	getMissingDependencies() []string
+
+	// 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 baseModuleContext struct {
@@ -564,3 +570,32 @@
 	}
 	return sb.String()
 }
+
+func (m *baseModuleContext) 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(b/323382414): 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/config.go b/android/config.go
index d94a86f..567ebd8 100644
--- a/android/config.go
+++ b/android/config.go
@@ -18,7 +18,6 @@
 // product variables necessary for soong_build's operation.
 
 import (
-	"android/soong/shared"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -28,6 +27,9 @@
 	"strconv"
 	"strings"
 	"sync"
+	"unicode"
+
+	"android/soong/shared"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
@@ -90,8 +92,6 @@
 	ModuleActionsFile string
 	DocFile           string
 
-	MultitreeBuild bool
-
 	BuildFromSourceStub bool
 
 	EnsureAllowlistIntegrity bool
@@ -209,13 +209,20 @@
 		Bool(c.config.productVariables.ReleaseDefaultModuleBuildFromSource)
 }
 
+// Enables flagged apis annotated with READ_WRITE aconfig flags to be included in the stubs
+// and hiddenapi flags so that they are accessible at runtime
+func (c Config) ReleaseExportRuntimeApis() bool {
+	return Bool(c.config.productVariables.ExportRuntimeApis)
+}
+
 // Enables ABI monitoring of NDK libraries
 func (c Config) ReleaseNdkAbiMonitored() bool {
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_NDK_ABI_MONITORED")
 }
 
 func (c Config) ReleaseHiddenApiExportableStubs() bool {
-	return c.config.productVariables.GetBuildFlagBool("RELEASE_HIDDEN_API_EXPORTABLE_STUBS")
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_HIDDEN_API_EXPORTABLE_STUBS") ||
+		Bool(c.config.productVariables.HiddenapiExportableStubs)
 }
 
 // A DeviceConfig object represents the configuration for a particular device
@@ -280,10 +287,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
@@ -320,6 +323,18 @@
 	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
 
+// Checks if the string is a valid go identifier. This is equivalent to blueprint's definition
+// of an identifier, so it will match the same identifiers as those that can be used in bp files.
+func isGoIdentifier(ident string) bool {
+	for i, r := range ident {
+		valid := r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) && i > 0
+		if !valid {
+			return false
+		}
+	}
+	return len(ident) > 0
+}
+
 // loadFromConfigFile loads and decodes configuration options from a JSON file
 // in the current working directory.
 func loadFromConfigFile(configurable *ProductVariables, filename string) error {
@@ -355,6 +370,20 @@
 		Bool(configurable.GcovCoverage) ||
 			Bool(configurable.ClangCoverage))
 
+	// The go scanner's definition of identifiers is c-style identifiers, but allowing unicode's
+	// definition of letters and digits. This is the same scanner that blueprint uses, so it
+	// will allow the same identifiers as are valid in bp files.
+	for namespace := range configurable.VendorVars {
+		if !isGoIdentifier(namespace) {
+			return fmt.Errorf("soong config namespaces must be valid identifiers: %q", namespace)
+		}
+		for variable := range configurable.VendorVars[namespace] {
+			if !isGoIdentifier(variable) {
+				return fmt.Errorf("soong config variables must be valid identifiers: %q", variable)
+			}
+		}
+	}
+
 	// when Platform_sdk_final is true (or PLATFORM_VERSION_CODENAME is REL), use Platform_sdk_version;
 	// if false (pre-released version, for example), use Platform_sdk_codename.
 	if Bool(configurable.Platform_sdk_final) {
@@ -507,8 +536,6 @@
 		moduleListFile: cmdArgs.ModuleListFile,
 		fs:             pathtools.NewOsFs(absSrcDir),
 
-		MultitreeBuild: cmdArgs.MultitreeBuild,
-
 		buildFromSourceStub: cmdArgs.BuildFromSourceStub,
 	}
 
@@ -612,15 +639,19 @@
 		"framework-adservices":              {},
 		"framework-appsearch":               {},
 		"framework-bluetooth":               {},
+		"framework-configinfrastructure":    {},
 		"framework-connectivity":            {},
 		"framework-connectivity-t":          {},
+		"framework-devicelock":              {},
 		"framework-graphics":                {},
+		"framework-healthfitness":           {},
 		"framework-location":                {},
 		"framework-media":                   {},
 		"framework-mediaprovider":           {},
 		"framework-nfc":                     {},
 		"framework-ondevicepersonalization": {},
 		"framework-pdf":                     {},
+		"framework-pdf-v":                   {},
 		"framework-permission":              {},
 		"framework-permission-s":            {},
 		"framework-scheduling":              {},
@@ -1309,6 +1340,25 @@
 	return String(c.productVariables.VendorApiLevel)
 }
 
+func (c *config) PrevVendorApiLevel() string {
+	vendorApiLevel, err := strconv.Atoi(c.VendorApiLevel())
+	if err != nil {
+		panic(fmt.Errorf("Cannot parse vendor API level %s to an integer: %s",
+			c.VendorApiLevel(), err))
+	}
+	// The version before trunk stable is 34.
+	if vendorApiLevel == 202404 {
+		return "34"
+	}
+	if vendorApiLevel >= 1 && vendorApiLevel <= 34 {
+		return strconv.Itoa(vendorApiLevel - 1)
+	}
+	if vendorApiLevel < 202404 || vendorApiLevel%100 != 4 {
+		panic("Unknown vendor API level " + c.VendorApiLevel())
+	}
+	return strconv.Itoa(vendorApiLevel - 100)
+}
+
 func (c *config) VendorApiLevelFrozen() bool {
 	return c.productVariables.GetBuildFlagBool("RELEASE_BOARD_API_LEVEL_FROZEN")
 }
@@ -1466,18 +1516,18 @@
 }
 
 // AfdoProfile returns fully qualified path associated to the given module name
-func (c *deviceConfig) AfdoProfile(name string) (*string, error) {
+func (c *deviceConfig) AfdoProfile(name string) (string, error) {
 	for _, afdoProfile := range c.config.productVariables.AfdoProfiles {
 		split := strings.Split(afdoProfile, ":")
 		if len(split) != 3 {
-			return nil, fmt.Errorf("AFDO_PROFILES has invalid value: %s. "+
+			return "", fmt.Errorf("AFDO_PROFILES has invalid value: %s. "+
 				"The expected format is <module>:<fully-qualified-path-to-fdo_profile>", afdoProfile)
 		}
 		if split[0] == name {
-			return proptools.StringPtr(strings.Join([]string{split[1], split[2]}, ":")), nil
+			return strings.Join([]string{split[1], split[2]}, ":"), nil
 		}
 	}
-	return nil, nil
+	return "", nil
 }
 
 func (c *deviceConfig) VendorSepolicyDirs() []string {
@@ -1706,10 +1756,6 @@
 	return String(c.config.productVariables.PlatformSepolicyVersion)
 }
 
-func (c *deviceConfig) TotSepolicyVersion() string {
-	return String(c.config.productVariables.TotSepolicyVersion)
-}
-
 func (c *deviceConfig) PlatformSepolicyCompatVersions() []string {
 	return c.config.productVariables.PlatformSepolicyCompatVersions
 }
@@ -1893,6 +1939,10 @@
 	return c.config.productVariables.GenerateAidlNdkPlatformBackend
 }
 
+func (c *deviceConfig) AconfigContainerValidation() string {
+	return c.config.productVariables.AconfigContainerValidation
+}
+
 func (c *config) IgnorePrefer32OnDevice() bool {
 	return c.productVariables.IgnorePrefer32OnDevice
 }
@@ -1980,25 +2030,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",
 	}
@@ -2015,3 +2075,7 @@
 	}
 	return ret
 }
+
+func (c *config) BuildIgnoreApexContributionContents() []string {
+	return c.productVariables.BuildIgnoreApexContributionContents
+}
diff --git a/android/deptag.go b/android/deptag.go
index a15443b..c7ba4d3 100644
--- a/android/deptag.go
+++ b/android/deptag.go
@@ -43,3 +43,15 @@
 	}
 	return false
 }
+
+type PropagateAconfigValidationDependencyTag interface {
+	PropagateAconfigValidation() bool
+}
+
+type AlwaysPropagateAconfigValidationDependencyTag struct{}
+
+func (p AlwaysPropagateAconfigValidationDependencyTag) PropagateAconfigValidation() bool {
+	return true
+}
+
+var _ PropagateAconfigValidationDependencyTag = AlwaysPropagateAconfigValidationDependencyTag{}
diff --git a/android/module.go b/android/module.go
index b615ff5..000476c 100644
--- a/android/module.go
+++ b/android/module.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"crypto/md5"
 	"encoding/hex"
 	"encoding/json"
@@ -27,6 +26,8 @@
 	"sort"
 	"strings"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -2087,6 +2088,7 @@
 // caused by prebuilt_ prefix, or fully qualified module names.
 type sourceOrOutputDependencyTag struct {
 	blueprint.BaseDependencyTag
+	AlwaysPropagateAconfigValidationDependencyTag
 
 	// The name of the module.
 	moduleName string
diff --git a/android/ninja_deps.go b/android/ninja_deps.go
index 1d50a47..bdf465e 100644
--- a/android/ninja_deps.go
+++ b/android/ninja_deps.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/starlark_import"
 	"sort"
 )
 
@@ -43,11 +42,4 @@
 
 func (ninjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
 	ctx.AddNinjaFileDeps(ctx.Config().ninjaFileDeps()...)
-
-	deps, err := starlark_import.GetNinjaDeps()
-	if err != nil {
-		ctx.Errorf("Error running starlark code: %s", err)
-	} else {
-		ctx.AddNinjaFileDeps(deps...)
-	}
 }
diff --git a/android/packaging.go b/android/packaging.go
index 8873540..a8fb28d 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -85,6 +85,7 @@
 
 	// GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
 	GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
+	GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec
 
 	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
 	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
@@ -221,14 +222,18 @@
 	}
 }
 
-// See PackageModule.GatherPackagingSpecs
-func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
+func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
 	ctx.VisitDirectDeps(func(child Module) {
 		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
 		}
 		for _, ps := range child.TransitivePackagingSpecs() {
+			if filter != nil {
+				if !filter(ps) {
+					continue
+				}
+			}
 			if _, ok := m[ps.relPathInPackage]; !ok {
 				m[ps.relPathInPackage] = ps
 			}
@@ -237,6 +242,11 @@
 	return m
 }
 
+// See PackageModule.GatherPackagingSpecs
+func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
+	return p.GatherPackagingSpecsWithFilter(ctx, nil)
+}
+
 // CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into the specified directory.
 func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
diff --git a/android/path_properties.go b/android/path_properties.go
index bbfaa8c..ea92565 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -47,7 +47,7 @@
 	// tagged with `android:"path"`.
 	var pathProperties []string
 	for _, ps := range props {
-		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ps)...)
+		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ctx, ps)...)
 	}
 
 	// Remove duplicates to avoid multiple dependencies.
@@ -64,7 +64,7 @@
 // pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with
 // android:"path" to extract all their values from a property struct, returning them as a single
 // slice of strings.
-func pathPropertiesForPropertyStruct(ps interface{}) []string {
+func pathPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}) []string {
 	v := reflect.ValueOf(ps)
 	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
 		panic(fmt.Errorf("type %s is not a pointer to a struct", v.Type()))
@@ -106,6 +106,16 @@
 				ret = append(ret, sv.String())
 			case reflect.Slice:
 				ret = append(ret, sv.Interface().([]string)...)
+			case reflect.Struct:
+				intf := sv.Interface()
+				if configurable, ok := intf.(proptools.Configurable[string]); ok {
+					ret = append(ret, proptools.String(configurable.Evaluate(ctx)))
+				} else if configurable, ok := intf.(proptools.Configurable[[]string]); ok {
+					ret = append(ret, proptools.Slice(configurable.Evaluate(ctx))...)
+				} else {
+					panic(fmt.Errorf(`field %s in type %s has tag android:"path" but is not a string or slice of strings, it is a %s`,
+						v.Type().FieldByIndex(i).Name, v.Type(), sv.Type()))
+				}
 			default:
 				panic(fmt.Errorf(`field %s in type %s has tag android:"path" but is not a string or slice of strings, it is a %s`,
 					v.Type().FieldByIndex(i).Name, v.Type(), sv.Type()))
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 13cda9d..5a94a0f 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -534,6 +534,35 @@
 		for _, moduleInFamily := range allModulesInFamily {
 			if moduleInFamily.Name() != selectedModuleInFamily.Name() {
 				moduleInFamily.HideFromMake()
+				// If this is a prebuilt module, unset properties.UsePrebuilt
+				// properties.UsePrebuilt might evaluate to true via soong config var fallback mechanism
+				// Set it to false explicitly so that the following mutator does not replace rdeps to this unselected prebuilt
+				if p := GetEmbeddedPrebuilt(moduleInFamily); p != nil {
+					p.properties.UsePrebuilt = false
+				}
+			}
+		}
+	}
+	// Do a validation pass to make sure that multiple prebuilts of a specific module are not selected.
+	// This might happen if the prebuilts share the same soong config var namespace.
+	// 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 {
+			continue
+		}
+		if p := GetEmbeddedPrebuilt(moduleInFamily); p != nil && p.properties.UsePrebuilt {
+			if selectedPrebuilt == nil {
+				selectedPrebuilt = moduleInFamily
+			} else {
+				ctx.ModuleErrorf("Multiple prebuilt modules %v and %v have been marked as preferred for this source module. "+
+					"Please add the appropriate prebuilt module to apex_contributions for this release config.", selectedPrebuilt.Name(), moduleInFamily.Name())
 			}
 		}
 	}
@@ -562,7 +591,7 @@
 						// If we do not special-case this here, rdeps referring to a java_sdk_library in next builds via libs
 						// will get prebuilt stubs
 						// TODO (b/308187268): Remove this after the apexes have been added to apex_contributions
-						if psi.IsSelected(*sdkLibrary.SdkLibraryName()) {
+						if psi.IsSelected(name) {
 							return false
 						}
 					}
@@ -625,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 {
@@ -639,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
 	}
 
@@ -647,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/prebuilt_test.go b/android/prebuilt_test.go
index 4a69628..2241b08 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -762,3 +762,45 @@
 		}
 		`, selectMainlineModuleContritbutions)
 }
+
+// Test that apex_contributions of prebuilt modules are ignored in coverage builds
+func TestSourceIsSelectedInCoverageBuilds(t *testing.T) {
+	prebuiltMainlineContributions := GroupFixturePreparers(
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.BuildFlags = map[string]string{
+				"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_prebuilt_apex_contributions",
+			}
+		}),
+		FixtureMergeEnv(map[string]string{
+			"EMMA_INSTRUMENT_FRAMEWORK": "true",
+		}),
+	)
+	bp := `
+		source {
+			name: "foo",
+		}
+		prebuilt {
+			name: "foo",
+			srcs: ["prebuilt_file"],
+		}
+		apex_contributions {
+			name: "my_prebuilt_apex_contributions",
+			api_domain: "my_mainline_module",
+			contents: [
+			  "prebuilt_foo",
+			],
+		}
+		all_apex_contributions {
+			name: "all_apex_contributions",
+		}
+		`
+	ctx := GroupFixturePreparers(
+		PrepareForTestWithArchMutator,
+		PrepareForTestWithPrebuilts,
+		FixtureRegisterWithContext(registerTestPrebuiltModules),
+		prebuiltMainlineContributions).RunTestWithBp(t, bp)
+	source := ctx.ModuleForTests("foo", "android_common").Module()
+	AssertBoolEquals(t, "Source should be preferred in coverage builds", true, !source.IsHideFromMake())
+	prebuilt := ctx.ModuleForTests("prebuilt_foo", "android_common").Module()
+	AssertBoolEquals(t, "Prebuilt should not be preferred in coverage builds", false, !prebuilt.IsHideFromMake())
+}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index e8dbd48..85e29bd 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -1157,11 +1157,15 @@
 
 // OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox,
 // and will be the temporary output directory managed by sbox, not the final one.
-func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand {
+func (c *RuleBuilderCommand) OutputDir(subPathComponents ...string) *RuleBuilderCommand {
 	if !c.rule.sbox {
 		panic("OutputDir only valid with Sbox")
 	}
-	return c.Text(sboxOutDir)
+	path := sboxOutDir
+	if len(subPathComponents) > 0 {
+		path = filepath.Join(append([]string{sboxOutDir}, subPathComponents...)...)
+	}
+	return c.Text(path)
 }
 
 // DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
diff --git a/android/selects_test.go b/android/selects_test.go
new file mode 100644
index 0000000..aa9c521
--- /dev/null
+++ b/android/selects_test.go
@@ -0,0 +1,316 @@
+// 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: "basic paths",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_paths: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["foo.txt"],
+					"b": ["bar.txt"],
+					_: ["baz.txt"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_paths: &[]string{"baz.txt"},
+			},
+		},
+		{
+			name: "paths with module references",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_paths: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": [":a"],
+					"b": [":b"],
+					_: [":c"],
+				}),
+			}
+			`,
+			expectedError: `"foo" depends on undefined module "c"`,
+		},
+		{
+			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
+	my_paths       *[]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,
+    my_paths: %s,
+}`, myBoolStr, myStringStr, p.my_string_list, p.my_paths)
+}
+
+var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
+
+type selectsMockModuleProperties struct {
+	My_bool        proptools.Configurable[bool]
+	My_string      proptools.Configurable[string]
+	My_string_list proptools.Configurable[[]string]
+	My_paths       proptools.Configurable[[]string] `android:"path"`
+}
+
+type selectsMockModule struct {
+	ModuleBase
+	DefaultableModuleBase
+	properties selectsMockModuleProperties
+}
+
+func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	SetProvider(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),
+		my_paths:       p.properties.My_paths.Evaluate(ctx),
+	})
+}
+
+func newSelectsMockModule() Module {
+	m := &selectsMockModule{}
+	m.AddProperties(&m.properties)
+	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
+	InitDefaultableModule(m)
+	return m
+}
diff --git a/android/singleton.go b/android/singleton.go
index e0e552e..ccddeaf 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -250,8 +250,8 @@
 }
 
 func (s *singletonContextAdaptor) ModuleVariantsFromName(referer Module, name string) []Module {
-	// get qualified module name for visibility enforcement
-	qualified := createQualifiedModuleName(s.ModuleName(referer), s.ModuleDir(referer))
+	// get module reference for visibility enforcement
+	qualified := createVisibilityModuleReference(s.ModuleName(referer), s.ModuleDir(referer), s.ModuleType(referer))
 
 	modules := s.SingletonContext.ModuleVariantsFromName(referer, name)
 	result := make([]Module, 0, len(modules))
@@ -262,7 +262,7 @@
 			depDir := s.ModuleDir(module)
 			depQualified := qualifiedModuleName{depDir, depName}
 			// Targets are always visible to other targets in their own package.
-			if depQualified.pkg != qualified.pkg {
+			if depQualified.pkg != qualified.name.pkg {
 				rule := effectiveVisibilityRules(s.Config(), depQualified)
 				if !rule.matches(qualified) {
 					s.ModuleErrorf(referer, "module %q references %q which is not visible to this module\nYou may need to add %q to its visibility",
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index d525bdc..c78b726 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -681,6 +681,14 @@
 	if !propStruct.IsValid() {
 		return nil, nil
 	}
+	if err := s.printfIntoPropertyRecursive(nil, propStruct, configValue); err != nil {
+		return nil, err
+	}
+
+	return values.Interface(), nil
+}
+
+func (s *valueVariable) printfIntoPropertyRecursive(fieldName []string, propStruct reflect.Value, configValue string) error {
 	for i := 0; i < propStruct.NumField(); i++ {
 		field := propStruct.Field(i)
 		kind := field.Kind()
@@ -695,23 +703,31 @@
 		case reflect.String:
 			err := printfIntoProperty(field, configValue)
 			if err != nil {
-				return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				return fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, strings.Join(fieldName, "."), err)
 			}
 		case reflect.Slice:
 			for j := 0; j < field.Len(); j++ {
 				err := printfIntoProperty(field.Index(j), configValue)
 				if err != nil {
-					return nil, fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, propStruct.Type().Field(i).Name, err)
+					fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+					return fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, strings.Join(fieldName, "."), err)
 				}
 			}
 		case reflect.Bool:
 			// Nothing to do
+		case reflect.Struct:
+			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+			if err := s.printfIntoPropertyRecursive(fieldName, field, configValue); err != nil {
+				return err
+			}
+			fieldName = fieldName[:len(fieldName)-1]
 		default:
-			return nil, fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, propStruct.Type().Field(i).Name, kind)
+			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+			return fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, strings.Join(fieldName, "."), kind)
 		}
 	}
-
-	return values.Interface(), nil
+	return nil
 }
 
 func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
index db2c83c..1da0b49 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -429,6 +429,76 @@
 	}
 }
 
+func Test_PropertiesToApply_Value_Nested(t *testing.T) {
+	mt, _ := newModuleType(&ModuleTypeProperties{
+		Module_type:      "foo",
+		Config_namespace: "bar",
+		Value_variables:  []string{"my_value_var"},
+		Properties:       []string{"a.b"},
+	})
+	type properties struct {
+		A struct {
+			B string
+		}
+	}
+	conditionsDefault := &properties{
+		A: struct{ B string }{
+			B: "default",
+		},
+	}
+	type valueVarProps struct {
+		A struct {
+			B string
+		}
+		Conditions_default *properties
+	}
+	actualProps := &struct {
+		Soong_config_variables valueSoongConfigVars
+	}{
+		Soong_config_variables: valueSoongConfigVars{
+			My_value_var: &valueVarProps{
+				A: struct{ B string }{
+					B: "A.B=%s",
+				},
+				Conditions_default: conditionsDefault,
+			},
+		},
+	}
+	props := reflect.ValueOf(actualProps)
+
+	testCases := []struct {
+		name      string
+		config    SoongConfig
+		wantProps []interface{}
+	}{
+		{
+			name:      "no_vendor_config",
+			config:    Config(map[string]string{}),
+			wantProps: []interface{}{conditionsDefault},
+		},
+		{
+			name:   "value_var_set",
+			config: Config(map[string]string{"my_value_var": "Hello"}),
+			wantProps: []interface{}{&properties{
+				A: struct{ B string }{
+					B: "A.B=Hello",
+				},
+			}},
+		},
+	}
+
+	for _, tc := range testCases {
+		gotProps, err := PropertiesToApply(mt, props, tc.config)
+		if err != nil {
+			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
+		}
+
+		if !reflect.DeepEqual(gotProps, tc.wantProps) {
+			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
+		}
+	}
+}
+
 func Test_PropertiesToApply_String_Error(t *testing.T) {
 	mt, _ := newModuleType(&ModuleTypeProperties{
 		Module_type:      "foo",
diff --git a/android/test_suites.go b/android/test_suites.go
index 9ded998..adcc15a 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -24,6 +24,7 @@
 
 type testSuiteFiles struct {
 	robolectric WritablePath
+	ravenwood   WritablePath
 }
 
 type TestSuiteModule interface {
@@ -47,12 +48,15 @@
 	})
 
 	t.robolectric = robolectricTestSuite(ctx, files["robolectric-tests"])
-
 	ctx.Phony("robolectric-tests", t.robolectric)
+
+	t.ravenwood = ravenwoodTestSuite(ctx, files["ravenwood-tests"])
+	ctx.Phony("ravenwood-tests", t.ravenwood)
 }
 
 func (t *testSuiteFiles) MakeVars(ctx MakeVarsContext) {
 	ctx.DistForGoal("robolectric-tests", t.robolectric)
+	ctx.DistForGoal("ravenwood-tests", t.ravenwood)
 }
 
 func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
@@ -74,3 +78,23 @@
 
 	return outputFile
 }
+
+func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
+	var installedPaths InstallPaths
+	for _, module := range SortedKeys(files) {
+		installedPaths = append(installedPaths, files[module]...)
+	}
+	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
+
+	outputFile := PathForOutput(ctx, "packaging", "ravenwood-tests.zip")
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", outputFile).
+		FlagWithArg("-P ", "host/testcases").
+		FlagWithArg("-C ", testCasesDir.String()).
+		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
+		Flag("-sha256")
+	rule.Build("ravenwood_tests_zip", "ravenwood-tests.zip")
+
+	return outputFile
+}
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/android/variable.go b/android/variable.go
index a4917c5..73f5bfd 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -383,7 +383,6 @@
 
 	BoardSepolicyVers       *string `json:",omitempty"`
 	PlatformSepolicyVersion *string `json:",omitempty"`
-	TotSepolicyVersion      *string `json:",omitempty"`
 
 	SystemExtSepolicyPrebuiltApiDir *string `json:",omitempty"`
 	ProductSepolicyPrebuiltApiDir   *string `json:",omitempty"`
@@ -495,6 +494,14 @@
 	BuildFlags map[string]string `json:",omitempty"`
 
 	BuildFromSourceStub *bool `json:",omitempty"`
+
+	BuildIgnoreApexContributionContents []string `json:",omitempty"`
+
+	HiddenapiExportableStubs *bool `json:",omitempty"`
+
+	ExportRuntimeApis *bool `json:",omitempty"`
+
+	AconfigContainerValidation string `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
diff --git a/android/visibility.go b/android/visibility.go
index 3130135..79a534f 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -57,12 +57,29 @@
 
 var visibilityRuleRegexp = regexp.MustCompile(visibilityRulePattern)
 
+type visibilityModuleReference struct {
+	name              qualifiedModuleName
+	isPartitionModule bool
+}
+
+func createVisibilityModuleReference(name, dir, typ string) visibilityModuleReference {
+	isPartitionModule := false
+	switch typ {
+	case "android_filesystem", "android_system_image":
+		isPartitionModule = true
+	}
+	return visibilityModuleReference{
+		name:              createQualifiedModuleName(name, dir),
+		isPartitionModule: isPartitionModule,
+	}
+}
+
 // A visibility rule is associated with a module and determines which other modules it is visible
 // to, i.e. which other modules can depend on the rule's module.
 type visibilityRule interface {
 	// Check to see whether this rules matches m.
 	// Returns true if it does, false otherwise.
-	matches(m qualifiedModuleName) bool
+	matches(m visibilityModuleReference) bool
 
 	String() string
 }
@@ -108,8 +125,10 @@
 // ["//visibility:private"].
 type compositeRule []visibilityRule
 
+var _ visibilityRule = compositeRule{}
+
 // A compositeRule matches if and only if any of its rules matches.
-func (c compositeRule) matches(m qualifiedModuleName) bool {
+func (c compositeRule) matches(m visibilityModuleReference) bool {
 	for _, r := range c {
 		if r.matches(m) {
 			return true
@@ -135,8 +154,10 @@
 	pkg string
 }
 
-func (r packageRule) matches(m qualifiedModuleName) bool {
-	return m.pkg == r.pkg
+var _ visibilityRule = packageRule{}
+
+func (r packageRule) matches(m visibilityModuleReference) bool {
+	return m.name.pkg == r.pkg
 }
 
 func (r packageRule) String() string {
@@ -149,8 +170,10 @@
 	pkgPrefix string
 }
 
-func (r subpackagesRule) matches(m qualifiedModuleName) bool {
-	return isAncestor(r.pkgPrefix, m.pkg)
+var _ visibilityRule = subpackagesRule{}
+
+func (r subpackagesRule) matches(m visibilityModuleReference) bool {
+	return isAncestor(r.pkgPrefix, m.name.pkg)
 }
 
 func isAncestor(p1 string, p2 string) bool {
@@ -168,7 +191,9 @@
 // visibilityRule for //visibility:public
 type publicRule struct{}
 
-func (r publicRule) matches(_ qualifiedModuleName) bool {
+var _ visibilityRule = publicRule{}
+
+func (r publicRule) matches(_ visibilityModuleReference) bool {
 	return true
 }
 
@@ -179,7 +204,9 @@
 // visibilityRule for //visibility:private
 type privateRule struct{}
 
-func (r privateRule) matches(_ qualifiedModuleName) bool {
+var _ visibilityRule = privateRule{}
+
+func (r privateRule) matches(_ visibilityModuleReference) bool {
 	return false
 }
 
@@ -187,6 +214,19 @@
 	return "//visibility:private"
 }
 
+// visibilityRule for //visibility:any_partition
+type anyPartitionRule struct{}
+
+var _ visibilityRule = anyPartitionRule{}
+
+func (r anyPartitionRule) matches(m visibilityModuleReference) bool {
+	return m.isPartitionModule
+}
+
+func (r anyPartitionRule) String() string {
+	return "//visibility:any_partition"
+}
+
 var visibilityRuleMap = NewOnceKey("visibilityRuleMap")
 
 // The map from qualifiedModuleName to visibilityRule.
@@ -237,13 +277,10 @@
 
 // Checks the per-module visibility rule lists before defaults expansion.
 func visibilityRuleChecker(ctx BottomUpMutatorContext) {
-	qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir())
-	if m, ok := ctx.Module().(Module); ok {
-		visibilityProperties := m.visibilityProperties()
-		for _, p := range visibilityProperties {
-			if visibility := p.getStrings(); visibility != nil {
-				checkRules(ctx, qualified.pkg, p.getName(), visibility)
-			}
+	visibilityProperties := ctx.Module().visibilityProperties()
+	for _, p := range visibilityProperties {
+		if visibility := p.getStrings(); visibility != nil {
+			checkRules(ctx, ctx.ModuleDir(), p.getName(), visibility)
 		}
 	}
 }
@@ -267,6 +304,9 @@
 		if pkg == "visibility" {
 			switch name {
 			case "private", "public":
+			case "any_partition":
+				// any_partition can be used with another visibility fields
+				continue
 			case "legacy_public":
 				ctx.PropertyErrorf(property, "//visibility:legacy_public must not be used")
 				continue
@@ -305,10 +345,7 @@
 //
 // See ../README.md#Visibility for information on the format of the visibility rules.
 func visibilityRuleGatherer(ctx BottomUpMutatorContext) {
-	m, ok := ctx.Module().(Module)
-	if !ok {
-		return
-	}
+	m := ctx.Module()
 
 	qualifiedModuleId := m.qualifiedModuleId(ctx)
 	currentPkg := qualifiedModuleId.pkg
@@ -355,6 +392,8 @@
 				hasNonPrivateRule = false
 				// This does not actually create a rule so continue onto the next rule.
 				continue
+			case "any_partition":
+				r = anyPartitionRule{}
 			}
 		} else {
 			switch name {
@@ -395,10 +434,7 @@
 
 func isAllowedFromOutsideVendor(pkg string, name string) bool {
 	if pkg == "vendor" {
-		if name == "__subpackages__" {
-			return true
-		}
-		return false
+		return name == "__subpackages__"
 	}
 
 	return !isAncestor("vendor", pkg)
@@ -434,11 +470,7 @@
 }
 
 func visibilityRuleEnforcer(ctx TopDownMutatorContext) {
-	if _, ok := ctx.Module().(Module); !ok {
-		return
-	}
-
-	qualified := createQualifiedModuleName(ctx.ModuleName(), ctx.ModuleDir())
+	qualified := createVisibilityModuleReference(ctx.ModuleName(), ctx.ModuleDir(), ctx.ModuleType())
 
 	// Visit all the dependencies making sure that this module has access to them all.
 	ctx.VisitDirectDeps(func(dep Module) {
@@ -453,7 +485,7 @@
 		depQualified := qualifiedModuleName{depDir, depName}
 
 		// Targets are always visible to other targets in their own package.
-		if depQualified.pkg == qualified.pkg {
+		if depQualified.pkg == qualified.name.pkg {
 			return
 		}
 
@@ -478,7 +510,7 @@
 	if ok {
 		rule = value.(compositeRule)
 	} else {
-		rule = packageDefaultVisibility(config, qualified)
+		rule = packageDefaultVisibility(moduleToVisibilityRule, qualified)
 	}
 
 	// If no rule is specified then return the default visibility rule to avoid
@@ -494,8 +526,7 @@
 	return qualified
 }
 
-func packageDefaultVisibility(config Config, moduleId qualifiedModuleName) compositeRule {
-	moduleToVisibilityRule := moduleToVisibilityRuleMap(config)
+func packageDefaultVisibility(moduleToVisibilityRule *sync.Map, moduleId qualifiedModuleName) compositeRule {
 	packageQualifiedId := moduleId.getContainingPackageId()
 	for {
 		value, ok := moduleToVisibilityRule.Load(packageQualifiedId)
@@ -574,10 +605,12 @@
 
 	rule := effectiveVisibilityRules(ctx.Config(), qualified)
 
+	currentModule := createVisibilityModuleReference(moduleName, dir, ctx.OtherModuleType(module))
+
 	// Modules are implicitly visible to other modules in the same package,
 	// without checking the visibility rules. Here we need to add that visibility
 	// explicitly.
-	if !rule.matches(qualified) {
+	if !rule.matches(currentModule) {
 		if len(rule) == 1 {
 			if _, ok := rule[0].(privateRule); ok {
 				// If the rule is //visibility:private we can't append another
diff --git a/android/visibility_test.go b/android/visibility_test.go
index a66f0b6..bb43b1f 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -1904,6 +1904,58 @@
 				}`),
 		},
 	},
+	{
+		name: "any_partition visibility works",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				android_filesystem {
+					name: "foo",
+					deps: ["bar"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				package(default_visibility=["//visibility:private"])
+				mock_library {
+					name: "bar",
+					visibility: ["//visibility:any_partition"],
+				}`),
+		},
+	},
+	{
+		name: "any_partition visibility works with the other visibility",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				android_filesystem {
+					name: "foo",
+					deps: ["bar"],
+				}`),
+			"top2/Android.bp": []byte(``),
+			"top/nested/Android.bp": []byte(`
+				package(default_visibility=["//visibility:private"])
+				mock_library {
+					name: "bar",
+					visibility: [
+						"//top2",
+						"//visibility:any_partition"
+					],
+				}`),
+		},
+	},
+	{
+		name: "any_partition visibility doesn't work for non-partitions",
+		fs: MockFS{
+			"top/Android.bp": []byte(`
+				mock_library {
+					name: "foo",
+					deps: ["bar"],
+				}`),
+			"top/nested/Android.bp": []byte(`
+				mock_library {
+					name: "bar",
+					visibility: ["//visibility:any_partition"],
+				}`),
+		},
+		expectedErrors: []string{`module "foo" variant "android_common": depends on //top/nested:bar which is not visible to this module`},
+	},
 }
 
 func TestVisibility(t *testing.T) {
@@ -1925,6 +1977,8 @@
 					ctx.RegisterModuleType("mock_library", newMockLibraryModule)
 					ctx.RegisterModuleType("mock_parent", newMockParentFactory)
 					ctx.RegisterModuleType("mock_defaults", defaultsFactory)
+					// For testing //visibility:any_partition. The module type doesn't matter, just that it's registered under the name "android_filesystem"
+					ctx.RegisterModuleType("android_filesystem", newMockLibraryModule)
 				}),
 				prepareForTestWithFakePrebuiltModules,
 				// Add additional files to the mock filesystem
diff --git a/apex/aconfig_test.go b/apex/aconfig_test.go
new file mode 100644
index 0000000..a179dbf
--- /dev/null
+++ b/apex/aconfig_test.go
@@ -0,0 +1,701 @@
+// 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 apex
+
+import (
+	"testing"
+
+	"android/soong/aconfig/codegen"
+	"android/soong/android"
+	"android/soong/cc"
+	"android/soong/genrule"
+	"android/soong/java"
+	"android/soong/rust"
+	"github.com/google/blueprint/proptools"
+)
+
+var withAconfigValidationError = android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+	variables.AconfigContainerValidation = "error"
+	variables.BuildId = proptools.StringPtr("TEST.BUILD_ID")
+})
+
+func TestValidationAcrossContainersExportedPass(t *testing.T) {
+	testCases := []struct {
+		name string
+		bp   string
+	}{
+		{
+			name: "Java lib passes for exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					java_libs: [
+						"my_java_library_foo",
+					],
+					updatable: false,
+				}
+				java_library {
+					name: "my_java_library_foo",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					static_libs: ["my_java_aconfig_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+					exportable: true,
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					mode: "exported",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+		},
+		{
+			name: "Android app passes for exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					apps: [
+						"my_android_app_foo",
+					],
+					updatable: false,
+				}
+				android_app {
+					name: "my_android_app_foo",
+					srcs: ["foo/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					stl: "none",
+					static_libs: ["my_java_library_bar"],
+					apex_available: [ "myapex" ],
+				}
+				java_library {
+					name: "my_java_library_bar",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					static_libs: ["my_java_aconfig_library_bar"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_bar",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["bar.aconfig"],
+					exportable: true,
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_bar",
+					aconfig_declarations: "my_aconfig_declarations_bar",
+					mode: "exported",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+		},
+		{
+			name: "Cc lib passes for exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					native_shared_libs: [
+						"my_cc_library_bar",
+					],
+					binaries: [
+						"my_cc_binary_baz",
+					],
+					updatable: false,
+				}
+				cc_library {
+					name: "my_cc_library_bar",
+					srcs: ["foo/bar/MyClass.cc"],
+					static_libs: [
+						"my_cc_aconfig_library_bar",
+						"my_cc_aconfig_library_baz",
+					],
+					apex_available: [
+						"myapex",
+					],
+				}
+				cc_binary {
+					name: "my_cc_binary_baz",
+					srcs: ["foo/bar/MyClass.cc"],
+					static_libs: ["my_cc_aconfig_library_baz"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				cc_library {
+					name: "server_configurable_flags",
+					srcs: ["server_configurable_flags.cc"],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_bar",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["bar.aconfig"],
+					exportable: true,
+				}
+				cc_aconfig_library {
+					name: "my_cc_aconfig_library_bar",
+					aconfig_declarations: "my_aconfig_declarations_bar",
+					apex_available: [
+						"myapex",
+					],
+					mode: "exported",
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_baz",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["baz.aconfig"],
+					exportable: true,
+				}
+				cc_aconfig_library {
+					name: "my_cc_aconfig_library_baz",
+					aconfig_declarations: "my_aconfig_declarations_baz",
+					apex_available: [
+						"myapex",
+					],
+					mode: "exported",
+				}`,
+		},
+		{
+			name: "Rust lib passes for exported containers cross",
+			bp: apex_default_bp + `
+			apex {
+				name: "myapex",
+				manifest: ":myapex.manifest",
+				androidManifest: ":myapex.androidmanifest",
+				key: "myapex.key",
+				native_shared_libs: ["libmy_rust_library"],
+				binaries: ["my_rust_binary"],
+				updatable: false,
+			}
+			rust_library {
+				name: "libflags_rust", // test mock
+				crate_name: "flags_rust",
+				srcs: ["lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_library {
+				name: "liblazy_static", // test mock
+				crate_name: "lazy_static",
+				srcs: ["src/lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_ffi_shared {
+				name: "libmy_rust_library",
+				srcs: ["src/lib.rs"],
+				rustlibs: ["libmy_rust_aconfig_library_foo"],
+				crate_name: "my_rust_library",
+				apex_available: ["myapex"],
+			}
+			rust_binary {
+				name: "my_rust_binary",
+				srcs: ["foo/bar/MyClass.rs"],
+				rustlibs: ["libmy_rust_aconfig_library_bar"],
+				apex_available: ["myapex"],
+			}
+			aconfig_declarations {
+				name: "my_aconfig_declarations_foo",
+				package: "com.example.package",
+				container: "otherapex",
+				srcs: ["foo.aconfig"],
+			}
+			aconfig_declarations {
+				name: "my_aconfig_declarations_bar",
+				package: "com.example.package",
+				container: "otherapex",
+				srcs: ["bar.aconfig"],
+			}
+			rust_aconfig_library {
+				name: "libmy_rust_aconfig_library_foo",
+				aconfig_declarations: "my_aconfig_declarations_foo",
+				crate_name: "my_rust_aconfig_library_foo",
+				apex_available: ["myapex"],
+				mode: "exported",
+			}
+			rust_aconfig_library {
+				name: "libmy_rust_aconfig_library_bar",
+				aconfig_declarations: "my_aconfig_declarations_bar",
+				crate_name: "my_rust_aconfig_library_bar",
+				apex_available: ["myapex"],
+				mode: "exported",
+			}`,
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			android.GroupFixturePreparers(
+				java.PrepareForTestWithJavaDefaultModules,
+				cc.PrepareForTestWithCcBuildComponents,
+				rust.PrepareForTestWithRustDefaultModules,
+				codegen.PrepareForTestWithAconfigBuildComponents,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				withAconfigValidationError,
+			).
+				RunTestWithBp(t, test.bp)
+		})
+	}
+}
+
+func TestValidationAcrossContainersNotExportedFail(t *testing.T) {
+	testCases := []struct {
+		name          string
+		expectedError string
+		bp            string
+	}{
+		{
+			name: "Java lib fails for non-exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					java_libs: [
+						"my_java_library_foo",
+					],
+					updatable: false,
+				}
+				java_library {
+					name: "my_java_library_foo",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					static_libs: ["my_java_aconfig_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+			expectedError: `.*my_java_library_foo/myapex depends on my_java_aconfig_library_foo/otherapex/production across containers`,
+		},
+		{
+			name: "Android app fails for non-exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					apps: [
+						"my_android_app_foo",
+					],
+					updatable: false,
+				}
+				android_app {
+					name: "my_android_app_foo",
+					srcs: ["foo/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					stl: "none",
+					static_libs: ["my_java_library_foo"],
+					apex_available: [ "myapex" ],
+				}
+				java_library {
+					name: "my_java_library_foo",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					static_libs: ["my_java_aconfig_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["bar.aconfig"],
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+			expectedError: `.*my_android_app_foo/myapex depends on my_java_aconfig_library_foo/otherapex/production across containers`,
+		},
+		{
+			name: "Cc lib fails for non-exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					native_shared_libs: [
+						"my_cc_library_foo",
+					],
+					updatable: false,
+				}
+				cc_library {
+					name: "my_cc_library_foo",
+					srcs: ["foo/bar/MyClass.cc"],
+					shared_libs: [
+						"my_cc_aconfig_library_foo",
+					],
+					apex_available: [
+						"myapex",
+					],
+				}
+				cc_library {
+					name: "server_configurable_flags",
+					srcs: ["server_configurable_flags.cc"],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+				}
+				cc_aconfig_library {
+					name: "my_cc_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+			expectedError: `.*my_cc_library_foo/myapex depends on my_cc_aconfig_library_foo/otherapex/production across containers`,
+		},
+		{
+			name: "Cc binary fails for non-exported containers cross",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					binaries: [
+						"my_cc_binary_foo",
+					],
+					updatable: false,
+				}
+				cc_library {
+					name: "my_cc_library_foo",
+					srcs: ["foo/bar/MyClass.cc"],
+					static_libs: [
+						"my_cc_aconfig_library_foo",
+					],
+					apex_available: [
+						"myapex",
+					],
+				}
+				cc_binary {
+					name: "my_cc_binary_foo",
+					srcs: ["foo/bar/MyClass.cc"],
+					static_libs: ["my_cc_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				cc_library {
+					name: "server_configurable_flags",
+					srcs: ["server_configurable_flags.cc"],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+				}
+				cc_aconfig_library {
+					name: "my_cc_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+			expectedError: `.*my_cc_binary_foo/myapex depends on my_cc_aconfig_library_foo/otherapex/production across containers`,
+		},
+		{
+			name: "Rust lib fails for non-exported containers cross",
+			bp: apex_default_bp + `
+			apex {
+				name: "myapex",
+				manifest: ":myapex.manifest",
+				androidManifest: ":myapex.androidmanifest",
+				key: "myapex.key",
+				native_shared_libs: ["libmy_rust_library"],
+				updatable: false,
+			}
+			rust_library {
+				name: "libflags_rust", // test mock
+				crate_name: "flags_rust",
+				srcs: ["lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_library {
+				name: "liblazy_static", // test mock
+				crate_name: "lazy_static",
+				srcs: ["src/lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_ffi_shared {
+				name: "libmy_rust_library",
+				srcs: ["src/lib.rs"],
+				rustlibs: ["libmy_rust_aconfig_library_foo"],
+				crate_name: "my_rust_library",
+				apex_available: ["myapex"],
+			}
+			aconfig_declarations {
+				name: "my_aconfig_declarations_foo",
+				package: "com.example.package",
+				container: "otherapex",
+				srcs: ["foo.aconfig"],
+			}
+			rust_aconfig_library {
+				name: "libmy_rust_aconfig_library_foo",
+				aconfig_declarations: "my_aconfig_declarations_foo",
+				crate_name: "my_rust_aconfig_library_foo",
+				apex_available: ["myapex"],
+			}`,
+			expectedError: `.*libmy_rust_aconfig_library_foo/myapex depends on libmy_rust_aconfig_library_foo/otherapex/production across containers`,
+		},
+		{
+			name: "Rust binary fails for non-exported containers cross",
+			bp: apex_default_bp + `
+			apex {
+				name: "myapex",
+				manifest: ":myapex.manifest",
+				androidManifest: ":myapex.androidmanifest",
+				key: "myapex.key",
+				binaries: ["my_rust_binary"],
+				updatable: false,
+			}
+			rust_library {
+				name: "libflags_rust", // test mock
+				crate_name: "flags_rust",
+				srcs: ["lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_library {
+				name: "liblazy_static", // test mock
+				crate_name: "lazy_static",
+				srcs: ["src/lib.rs"],
+				apex_available: ["myapex"],
+			}
+			rust_binary {
+				name: "my_rust_binary",
+				srcs: ["foo/bar/MyClass.rs"],
+				rustlibs: ["libmy_rust_aconfig_library_bar"],
+				apex_available: ["myapex"],
+			}
+			aconfig_declarations {
+				name: "my_aconfig_declarations_bar",
+				package: "com.example.package",
+				container: "otherapex",
+				srcs: ["bar.aconfig"],
+			}
+			rust_aconfig_library {
+				name: "libmy_rust_aconfig_library_bar",
+				aconfig_declarations: "my_aconfig_declarations_bar",
+				crate_name: "my_rust_aconfig_library_bar",
+				apex_available: ["myapex"],
+			}`,
+			expectedError: `.*libmy_rust_aconfig_library_bar/myapex depends on libmy_rust_aconfig_library_bar/otherapex/production across containers`,
+		},
+		{
+			name: "Aconfig validation propagate along sourceOrOutputDependencyTag",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					apps: [
+						"my_android_app_foo",
+					],
+					updatable: false,
+				}
+				android_app {
+					name: "my_android_app_foo",
+					srcs: ["foo/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					stl: "none",
+					static_libs: ["my_java_library_foo"],
+					apex_available: [ "myapex" ],
+				}
+				java_library {
+					name: "my_java_library_foo",
+					srcs: [":my_genrule_foo"],
+					sdk_version: "none",
+					system_modules: "none",
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations_group {
+						name: "my_aconfig_declarations_group_foo",
+						java_aconfig_libraries: [
+								"my_java_aconfig_library_foo",
+						],
+				}
+				filegroup {
+						name: "my_filegroup_foo_srcjars",
+						srcs: [
+								":my_aconfig_declarations_group_foo{.srcjars}",
+						],
+				}
+				genrule {
+						name: "my_genrule_foo",
+						srcs: [":my_filegroup_foo_srcjars"],
+						cmd: "cp $(in) $(out)",
+						out: ["my_genrule_foo.srcjar"],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["bar.aconfig"],
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+			expectedError: `.*my_android_app_foo/myapex depends on my_java_aconfig_library_foo/otherapex/production across containers`,
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			errorHandler := android.FixtureExpectsNoErrors
+			if test.expectedError != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
+			}
+			android.GroupFixturePreparers(
+				java.PrepareForTestWithJavaDefaultModules,
+				cc.PrepareForTestWithCcBuildComponents,
+				rust.PrepareForTestWithRustDefaultModules,
+				codegen.PrepareForTestWithAconfigBuildComponents,
+				genrule.PrepareForIntegrationTestWithGenrule,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				withAconfigValidationError,
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, test.bp)
+		})
+	}
+}
+
+func TestValidationNotPropagateAcrossShared(t *testing.T) {
+	testCases := []struct {
+		name string
+		bp   string
+	}{
+		{
+			name: "Java shared lib not propagate aconfig validation",
+			bp: apex_default_bp + `
+				apex {
+					name: "myapex",
+					manifest: ":myapex.manifest",
+					androidManifest: ":myapex.androidmanifest",
+					key: "myapex.key",
+					java_libs: [
+						"my_java_library_bar",
+					],
+					updatable: false,
+				}
+				java_library {
+					name: "my_java_library_bar",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					libs: ["my_java_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				java_library {
+					name: "my_java_library_foo",
+					srcs: ["foo/bar/MyClass.java"],
+					sdk_version: "none",
+					system_modules: "none",
+					static_libs: ["my_java_aconfig_library_foo"],
+					apex_available: [
+						"myapex",
+					],
+				}
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+				}
+				java_aconfig_library {
+					name: "my_java_aconfig_library_foo",
+					aconfig_declarations: "my_aconfig_declarations_foo",
+					apex_available: [
+						"myapex",
+					],
+				}`,
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			android.GroupFixturePreparers(
+				java.PrepareForTestWithJavaDefaultModules,
+				cc.PrepareForTestWithCcBuildComponents,
+				rust.PrepareForTestWithRustDefaultModules,
+				codegen.PrepareForTestWithAconfigBuildComponents,
+				PrepareForTestWithApexBuildComponents,
+				prepareForTestWithMyapex,
+				withAconfigValidationError,
+			).
+				RunTestWithBp(t, test.bp)
+		})
+	}
+}
diff --git a/apex/apex.go b/apex/apex.go
index 276ac80..32a3638 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -842,10 +842,12 @@
 		}
 
 		addDependenciesForNativeModules(ctx, deps, target, imageVariation)
-		ctx.AddFarVariationDependencies([]blueprint.Variation{
-			{Mutator: "os", Variation: target.OsVariation()},
-			{Mutator: "arch", Variation: target.ArchVariation()},
-		}, shBinaryTag, a.properties.Sh_binaries...)
+		if isPrimaryAbi {
+			ctx.AddFarVariationDependencies([]blueprint.Variation{
+				{Mutator: "os", Variation: target.OsVariation()},
+				{Mutator: "arch", Variation: target.ArchVariation()},
+			}, shBinaryTag, a.properties.Sh_binaries...)
+		}
 	}
 
 	// Common-arch dependencies come next
@@ -1165,6 +1167,7 @@
 		"com.android.ondevicepersonalization",
 		"com.android.os.statsd",
 		"com.android.permission",
+		"com.android.profiling",
 		"com.android.rkpd",
 		"com.android.scheduling",
 		"com.android.tethering",
@@ -2072,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() {
@@ -2087,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)
 			}
@@ -2115,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)
 			}
@@ -2138,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 {
@@ -2227,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) {
@@ -2260,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) {
@@ -2278,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",
@@ -2292,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",
@@ -2308,9 +2321,15 @@
 }
 
 func addAconfigFiles(vctx *visitorContext, ctx android.ModuleContext, module blueprint.Module) {
-	dep, _ := android.OtherModuleProvider(ctx, module, android.AconfigTransitiveDeclarationsInfoProvider)
-	if len(dep.AconfigFiles) > 0 && dep.AconfigFiles[ctx.ModuleName()] != nil {
-		vctx.aconfigFiles = append(vctx.aconfigFiles, dep.AconfigFiles[ctx.ModuleName()]...)
+	if dep, ok := android.OtherModuleProvider(ctx, module, android.AconfigTransitiveDeclarationsInfoProvider); ok {
+		if len(dep.AconfigFiles) > 0 && dep.AconfigFiles[ctx.ModuleName()] != nil {
+			vctx.aconfigFiles = append(vctx.aconfigFiles, dep.AconfigFiles[ctx.ModuleName()]...)
+		}
+	}
+
+	validationFlag := ctx.DeviceConfig().AconfigContainerValidation()
+	if validationFlag == "error" || validationFlag == "warning" {
+		android.VerifyAconfigBuildMode(ctx, ctx.ModuleName(), module, validationFlag == "error")
 	}
 }
 
@@ -2411,6 +2430,18 @@
 
 	// Set a provider for dexpreopt of bootjars
 	a.provideApexExportsInfo(ctx)
+
+	a.providePrebuiltInfo(ctx)
+}
+
+// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
+// with information about whether source or prebuilt of an apex was used during the build.
+func (a *apexBundle) providePrebuiltInfo(ctx android.ModuleContext) {
+	info := android.PrebuiltInfo{
+		Name:        a.Name(),
+		Is_prebuilt: false,
+	}
+	android.SetProvider(ctx, android.PrebuiltInfoProvider, info)
 }
 
 // Set a provider containing information about the jars and .prof provided by the apex
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index 25c0cc4..e6ebff2 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -17,6 +17,8 @@
 package apex
 
 import (
+	"encoding/json"
+
 	"github.com/google/blueprint"
 
 	"android/soong/android"
@@ -129,3 +131,43 @@
 	// Export check result to Make. The path is added to droidcore.
 	ctx.Strict("APEX_ALLOWED_DEPS_CHECK", s.allowedApexDepsInfoCheckResult.String())
 }
+
+func init() {
+	registerApexPrebuiltInfoComponents(android.InitRegistrationContext)
+}
+
+func registerApexPrebuiltInfoComponents(ctx android.RegistrationContext) {
+	ctx.RegisterParallelSingletonType("apex_prebuiltinfo_singleton", apexPrebuiltInfoFactory)
+}
+
+func apexPrebuiltInfoFactory() android.Singleton {
+	return &apexPrebuiltInfo{}
+}
+
+type apexPrebuiltInfo struct {
+	out android.WritablePath
+}
+
+func (a *apexPrebuiltInfo) GenerateBuildActions(ctx android.SingletonContext) {
+	prebuiltInfos := []android.PrebuiltInfo{}
+
+	ctx.VisitAllModules(func(m android.Module) {
+		prebuiltInfo, exists := android.SingletonModuleProvider(ctx, m, android.PrebuiltInfoProvider)
+		// Use prebuiltInfoProvider to filter out non apex soong modules.
+		// Use HideFromMake to filter out the unselected variants of a specific apex.
+		if exists && !m.IsHideFromMake() {
+			prebuiltInfos = append(prebuiltInfos, prebuiltInfo)
+		}
+	})
+
+	j, err := json.Marshal(prebuiltInfos)
+	if err != nil {
+		ctx.Errorf("Could not convert prebuilt info of apexes to json due to error: %v", err)
+	}
+	a.out = android.PathForOutput(ctx, "prebuilt_info.json")
+	android.WriteFileRule(ctx, a.out, string(j))
+}
+
+func (a *apexPrebuiltInfo) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droidcore", a.out)
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index d3959ec..add6083 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -790,6 +790,79 @@
 	}
 }
 
+func TestApexWithDessertSha(t *testing.T) {
+	ctx := testApex(t, `
+		apex_defaults {
+			name: "my_defaults",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: ":my-file-contexts",
+			updatable: false,
+		}
+		apex {
+			name: "myapex_30",
+			min_sdk_version: "30",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_current",
+			min_sdk_version: "current",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_none",
+			defaults: ["my_defaults"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		filegroup {
+			name: "my-file-contexts",
+			srcs: ["product_specific_file_contexts"],
+		}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}), android.FixtureModifyProductVariables(
+		func(variables android.FixtureProductVariables) {
+			variables.Unbundled_build = proptools.BoolPtr(true)
+			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(false)
+		}), android.FixtureMergeEnv(map[string]string{
+		"UNBUNDLED_BUILD_TARGET_SDK_WITH_DESSERT_SHA": "UpsideDownCake.abcdefghijklmnopqrstuvwxyz123456",
+	}))
+
+	testCases := []struct {
+		module        string
+		minSdkVersion string
+	}{
+		{
+			module:        "myapex_30",
+			minSdkVersion: "30",
+		},
+		{
+			module:        "myapex_current",
+			minSdkVersion: "UpsideDownCake.abcdefghijklmnopqrstuvwxyz123456",
+		},
+		{
+			module:        "myapex_none",
+			minSdkVersion: "UpsideDownCake.abcdefghijklmnopqrstuvwxyz123456",
+		},
+	}
+	for _, tc := range testCases {
+		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module)
+		args := module.Rule("apexRule").Args
+		optFlags := args["opt_flags"]
+		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
+			t.Errorf("%s: Expected min_sdk_version=%s, got: %s", tc.module, tc.minSdkVersion, optFlags)
+		}
+	}
+}
+
 func TestFileContexts(t *testing.T) {
 	for _, vendor := range []bool{true, false} {
 		prop := ""
@@ -1995,7 +2068,7 @@
 
 	// Ensure that mylib links with "current" LLNDK
 	libFlags := names(mylib.Rule("ld").Args["libFlags"])
-	ensureListContains(t, libFlags, "out/soong/.intermediates/libbar/"+vendorVariant+"_shared_current/libbar.so")
+	ensureListContains(t, libFlags, "out/soong/.intermediates/libbar/"+vendorVariant+"_shared/libbar.so")
 
 	// Ensure that mylib is targeting 29
 	ccRule := ctx.ModuleForTests("mylib", vendorVariant+"_static_apex29").Output("obj/mylib.o")
@@ -4973,6 +5046,7 @@
 			key: "myapex.key",
 			sh_binaries: ["myscript"],
 			updatable: false,
+			compile_multilib: "both",
 		}
 
 		apex_key {
@@ -6125,7 +6199,7 @@
 		`)
 	})
 
-	t.Run("Co-existing unflagged apexes should create a duplicate deapexer error in hiddenapi processing", func(t *testing.T) {
+	t.Run("Co-existing unflagged apexes should create a duplicate module error", func(t *testing.T) {
 		bp := `
 		// Source
 		apex {
@@ -6199,7 +6273,7 @@
 		}
 	`
 
-		testDexpreoptWithApexes(t, bp, "Multiple installable prebuilt APEXes provide ambiguous deapexers: prebuilt_myapex.v1 and prebuilt_myapex.v2", preparer, fragment)
+		testDexpreoptWithApexes(t, bp, "Multiple prebuilt modules prebuilt_myapex.v1 and prebuilt_myapex.v2 have been marked as preferred for this source module", preparer, fragment)
 	})
 
 }
@@ -10948,7 +11022,7 @@
 		}
 	`)
 
-	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("deps.zip").Implicits
+	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img").Implicits
 	android.AssertStringListDoesNotContain(t, "filesystem should not have libbar",
 		inputs.Strings(),
 		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
@@ -11042,25 +11116,23 @@
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
-	if len(copyCmds) != 5 {
+	if len(copyCmds) != 8 {
 		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[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$")
 
-	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
-	s = " " + combineAconfigRule.Args["cache_files"]
-	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
-	if len(aconfigArgs) != 2 {
-		t.Fatalf("Expected 2 commands, got %d in:\n%s", len(aconfigArgs), s)
+	inputs := []string{
+		"my_aconfig_declarations_foo/intermediate.pb",
+		"my_aconfig_declarations_bar/intermediate.pb",
 	}
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_bar/intermediate.pb")
-
-	buildParams := combineAconfigRule.BuildParams
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_bar/intermediate.pb")
-	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+	VerifyAconfigRule(t, &mod, "combine_aconfig_declarations", inputs, "android_common_myapex/aconfig_flags.pb", "", "")
+	VerifyAconfigRule(t, &mod, "create_aconfig_package_map_file", inputs, "android_common_myapex/package.map", "myapex", "package_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_map_file", inputs, "android_common_myapex/flag.map", "myapex", "flag_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_val_file", inputs, "android_common_myapex/flag.val", "myapex", "flag_val")
 }
 
 func TestAconfigFilesJavaAndCcDeps(t *testing.T) {
@@ -11168,30 +11240,24 @@
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
-	if len(copyCmds) != 9 {
-		t.Fatalf("Expected 9 commands, got %d in:\n%s", len(copyCmds), s)
+	if len(copyCmds) != 12 {
+		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[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$")
 
-	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
-	s = " " + combineAconfigRule.Args["cache_files"]
-	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
-	if len(aconfigArgs) != 3 {
-		t.Fatalf("Expected 3 commands, got %d in:\n%s", len(aconfigArgs), s)
+	inputs := []string{
+		"my_aconfig_declarations_foo/intermediate.pb",
+		"my_cc_library_bar/android_arm64_armv8-a_shared_apex10000/myapex/aconfig_merged.pb",
+		"my_aconfig_declarations_baz/intermediate.pb",
 	}
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_cc_library_bar/android_arm64_armv8-a_shared_apex10000/myapex/aconfig_merged.pb")
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_baz/intermediate.pb")
-
-	buildParams := combineAconfigRule.BuildParams
-	if len(buildParams.Inputs) != 3 {
-		t.Fatalf("Expected 3 input, got %d", len(buildParams.Inputs))
-	}
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_cc_library_bar/android_arm64_armv8-a_shared_apex10000/myapex/aconfig_merged.pb")
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_baz/intermediate.pb")
-	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+	VerifyAconfigRule(t, &mod, "combine_aconfig_declarations", inputs, "android_common_myapex/aconfig_flags.pb", "", "")
+	VerifyAconfigRule(t, &mod, "create_aconfig_package_map_file", inputs, "android_common_myapex/package.map", "myapex", "package_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_map_file", inputs, "android_common_myapex/flag.map", "myapex", "flag_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_val_file", inputs, "android_common_myapex/flag.val", "myapex", "flag_val")
 }
 
 func TestAconfigFilesRustDeps(t *testing.T) {
@@ -11315,28 +11381,45 @@
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
-	if len(copyCmds) != 23 {
-		t.Fatalf("Expected 23 commands, got %d in:\n%s", len(copyCmds), s)
+	if len(copyCmds) != 26 {
+		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[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$")
 
-	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
-	s = " " + combineAconfigRule.Args["cache_files"]
+	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", "", "")
+	VerifyAconfigRule(t, &mod, "create_aconfig_package_map_file", inputs, "android_common_myapex/package.map", "myapex", "package_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_map_file", inputs, "android_common_myapex/flag.map", "myapex", "flag_map")
+	VerifyAconfigRule(t, &mod, "create_aconfig_flag_val_file", inputs, "android_common_myapex/flag.val", "myapex", "flag_val")
+}
+
+func VerifyAconfigRule(t *testing.T, mod *android.TestingModule, desc string, inputs []string, output string, container string, file_type string) {
+	aconfigRule := mod.Description(desc)
+	s := " " + aconfigRule.Args["cache_files"]
 	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
-	if len(aconfigArgs) != 2 {
-		t.Fatalf("Expected 2 commands, got %d in:\n%s", len(aconfigArgs), s)
+	if len(aconfigArgs) != len(inputs) {
+		t.Fatalf("Expected %d commands, got %d in:\n%s", len(inputs), len(aconfigArgs), s)
 	}
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, aconfigArgs, "my_rust_binary/android_arm64_armv8-a_apex10000/myapex/aconfig_merged.pb")
 
-	buildParams := combineAconfigRule.BuildParams
-	if len(buildParams.Inputs) != 2 {
-		t.Fatalf("Expected 3 input, got %d", len(buildParams.Inputs))
+	ensureEquals(t, container, aconfigRule.Args["container"])
+	ensureEquals(t, file_type, aconfigRule.Args["file_type"])
+
+	buildParams := aconfigRule.BuildParams
+	for _, input := range inputs {
+		android.EnsureListContainsSuffix(t, aconfigArgs, input)
+		android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), input)
 	}
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_aconfig_declarations_foo/intermediate.pb")
-	android.EnsureListContainsSuffix(t, buildParams.Inputs.Strings(), "my_rust_binary/android_arm64_armv8-a_apex10000/myapex/aconfig_merged.pb")
-	ensureContains(t, buildParams.Output.String(), "android_common_myapex/aconfig_flags.pb")
+
+	ensureContains(t, buildParams.Output.String(), output)
 }
 
 func TestAconfigFilesOnlyMatchCurrentApex(t *testing.T) {
diff --git a/apex/builder.go b/apex/builder.go
index 3078863..6ad282a 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -85,6 +85,18 @@
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 }
 
+type createStorageStruct struct {
+	Output_file string
+	Desc        string
+	File_type   string
+}
+
+var createStorageInfo = []createStorageStruct{
+	{"package.map", "create_aconfig_package_map_file", "package_map"},
+	{"flag.map", "create_aconfig_flag_map_file", "flag_map"},
+	{"flag.val", "create_aconfig_flag_val_file", "flag_val"},
+}
+
 var (
 	apexManifestRule = pctx.StaticRule("apexManifestRule", blueprint.RuleParams{
 		Command: `rm -f $out && ${jsonmodify} $in ` +
@@ -633,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{
@@ -645,9 +658,28 @@
 			},
 		})
 
-		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)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        aconfig.CreateStorageRule,
+				Inputs:      a.aconfigFiles,
+				Output:      outputFile,
+				Description: info.Desc,
+				Args: map[string]string{
+					"container":   ctx.ModuleName(),
+					"file_type":   info.File_type,
+					"cache_files": android.JoinPathsWithPrefix(a.aconfigFiles, "--cache "),
+				},
+			})
+
+			copyCommands = append(copyCommands, "cp -f "+outputFile.String()+" "+aconfigDest)
+			implicitInputs = append(implicitInputs, outputFile)
+			defaultReadOnlyFiles = append(defaultReadOnlyFiles, "etc/"+outputFile.Base())
+		}
 	}
 
 	////////////////////////////////////////////////////////////////////////////////////
@@ -695,18 +727,20 @@
 	if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
 		minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 
-		if java.UseApiFingerprint(ctx) {
-			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
-			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+		if useApiFingerprint, fingerprintMinSdkVersion, fingerprintDeps :=
+			java.UseApiFingerprint(ctx); useApiFingerprint {
+			minSdkVersion = fingerprintMinSdkVersion
+			implicitInputs = append(implicitInputs, fingerprintDeps)
 		}
 	}
 	// apex module doesn't have a concept of target_sdk_version, hence for the time
 	// being targetSdkVersion == default targetSdkVersion of the branch.
 	targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
 
-	if java.UseApiFingerprint(ctx) {
-		targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
-		implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+	if useApiFingerprint, fingerprintTargetSdkVersion, fingerprintDeps :=
+		java.UseApiFingerprint(ctx); useApiFingerprint {
+		targetSdkVersion = fingerprintTargetSdkVersion
+		implicitInputs = append(implicitInputs, fingerprintDeps)
 	}
 	optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
 	optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
@@ -910,7 +944,7 @@
 	var validations android.Paths
 	validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile.OutputPath, imageDir.OutputPath))
 	// TODO(b/279688635) deapexer supports [ext4]
-	if suffix == imageApexSuffix && ext4 == a.payloadFsType {
+	if !a.testApex && suffix == imageApexSuffix && ext4 == a.payloadFsType {
 		validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile.OutputPath))
 	}
 	if !a.testApex && len(a.properties.Unwanted_transitive_deps) > 0 {
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 2bd3159..2be9c10 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -86,6 +86,7 @@
 					"bar-fragment",
 				],
 				updatable: false,
+				min_sdk_version: "30", // R
 			}
 
 			apex_key {
@@ -138,6 +139,7 @@
 				sdk_version: "none",
 				compile_dex: true,
 				permitted_packages: ["bar"],
+				min_sdk_version: "30", // R
 			}
 
 			java_sdk_library {
@@ -162,12 +164,12 @@
 		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
 	}
 
-	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
-	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
-	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/index.csv"}, info.IndexPaths)
+	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
+	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/index.csv"}, info.IndexPaths)
 
-	android.AssertArrayString(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-stub-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
-	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/filtered-stub-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
 // TestPlatformBootclasspath_LegacyPrebuiltFragment verifies that the
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index cebbae9..ea847e1 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -121,6 +121,11 @@
 	// List of systemserverclasspath fragments inside this prebuilt APEX bundle and for which this
 	// APEX bundle will create an APEX variant.
 	Exported_systemserverclasspath_fragments []string
+
+	// Path to the .prebuilt_info file of the prebuilt apex.
+	// In case of mainline modules, the .prebuilt_info file contains the build_id that was used to
+	// generate the prebuilt.
+	Prebuilt_info *string `android:"path"`
 }
 
 // initPrebuiltCommon initializes the prebuiltCommon structure and performs initialization of the
@@ -819,6 +824,20 @@
 	}
 }
 
+// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
+// with information about whether source or prebuilt of an apex was used during the build.
+func (p *prebuiltCommon) providePrebuiltInfo(ctx android.ModuleContext) {
+	info := android.PrebuiltInfo{
+		Name:        p.BaseModuleName(),
+		Is_prebuilt: true,
+	}
+	// If Prebuilt_info information is available in the soong module definition, add it to prebuilt_info.json.
+	if p.prebuiltCommonProperties.Prebuilt_info != nil {
+		info.Prebuilt_info_file_path = android.PathForModuleSrc(ctx, *p.prebuiltCommonProperties.Prebuilt_info).String()
+	}
+	android.SetProvider(ctx, android.PrebuiltInfoProvider, info)
+}
+
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	p.apexKeysPath = writeApexKeys(ctx, p)
 	// TODO(jungjw): Check the key validity.
@@ -846,6 +865,8 @@
 	// provide info used for generating the boot image
 	p.provideApexExportsInfo(ctx)
 
+	p.providePrebuiltInfo(ctx)
+
 	// Save the files that need to be made available to Make.
 	p.initApexFilesForAndroidMk(ctx)
 
@@ -1068,6 +1089,8 @@
 	// provide info used for generating the boot image
 	a.provideApexExportsInfo(ctx)
 
+	a.providePrebuiltInfo(ctx)
+
 	// Save the files that need to be made available to Make.
 	a.initApexFilesForAndroidMk(ctx)
 
diff --git a/bin/soongdbg b/bin/soongdbg
index 132a0f0..bfdbbde 100755
--- a/bin/soongdbg
+++ b/bin/soongdbg
@@ -2,9 +2,12 @@
 
 import argparse
 import fnmatch
+import html
+import io
 import json
 import os
 import pathlib
+import subprocess
 import types
 import sys
 
@@ -27,6 +30,7 @@
                 dep = get_or_make_node(self.nodes, d.id, None)
                 node.deps.add(dep)
                 dep.rdeps.add(node)
+                node.dep_tags.setdefault(dep, list()).append(d)
 
     def find_paths(self, id1, id2):
         # Throws KeyError if one of the names isn't found
@@ -60,6 +64,7 @@
         self.module = module
         self.deps = set()
         self.rdeps = set()
+        self.dep_tags = {}
 
 
 PROVIDERS = [
@@ -68,21 +73,35 @@
 ]
 
 
-def format_node_label(node):
-    if not node.module:
-        return node.id
-    if node.module.debug:
-        module_debug = f"<tr><td>{node.module.debug}</td></tr>"
-    else:
-        module_debug = ""
+def format_dep_label(node, dep):
+    tags = node.dep_tags.get(dep)
+    labels = []
+    if tags:
+        labels = [tag.tag_type.split("/")[-1] for tag in tags]
+        labels = sorted(set(labels))
+    if labels:
+        result = "<<table border=\"0\" cellborder=\"0\" cellspacing=\"0\" cellpadding=\"0\">"
+        for label in labels:
+            result += f"<tr><td>{label}</td></tr>"
+        result += "</table>>"
+        return result
 
-    result = (f"<<table border=\"0\" cellborder=\"0\" cellspacing=\"0\" cellpadding=\"0\">"
-            + f"<tr><td><b>{node.module.name}</b></td></tr>"
-            + module_debug
-            + f"<tr><td>{node.module.type}</td></tr>")
-    for p in node.module.providers:
-        if p.type in PROVIDERS:
-            result += "<tr><td><font color=\"#666666\">" + format_provider(p) + "</font></td></tr>"
+
+def format_node_label(node, module_formatter):
+    result = "<<table border=\"0\" cellborder=\"0\" cellspacing=\"0\" cellpadding=\"0\">"
+
+    # node name
+    result += f"<tr><td><b>{node.module.name if node.module else node.id}</b></td></tr>"
+
+    if node.module:
+        # node_type
+        result += f"<tr><td>{node.module.type}</td></tr>"
+
+        # module_formatter will return a list of rows
+        for row in module_formatter(node.module):
+            row = html.escape(row)
+            result += f"<tr><td><font color=\"#666666\">{row}</font></td></tr>"
+
     result += "</table>>"
     return result
 
@@ -190,22 +209,87 @@
             yield m
 
 
-def print_nodes(nodes):
-    print("digraph {")
+def print_args(parser):
+    parser.add_argument("--label", action="append", metavar="JQ_FILTER",
+                        help="jq query for each module metadata")
+    parser.add_argument("--deptags", action="store_true",
+                        help="show dependency tags (makes the graph much more complex)")
+
+    group = parser.add_argument_group("output formats",
+                                      "If no format is provided, a dot file will be written to"
+                                      + " stdout.")
+    output = group.add_mutually_exclusive_group()
+    output.add_argument("--dot", type=str, metavar="FILENAME",
+                        help="Write the graph to this file as dot (graphviz format)")
+    output.add_argument("--svg", type=str, metavar="FILENAME",
+                        help="Write the graph to this file as svg")
+
+
+def print_nodes(args, nodes, module_formatter):
+    # Generate the graphviz
+    dep_tag_id = 0
+    dot = io.StringIO()
+    dot.write("digraph {\n")
+    dot.write("node [shape=box];")
+
     for node in nodes:
-        print(f"\"{node.id}\"[label={format_node_label(node)}];")
+        dot.write(f"\"{node.id}\" [label={format_node_label(node, module_formatter)}];\n")
         for dep in node.deps:
             if dep in nodes:
-                print(f"\"{node.id}\" -> \"{dep.id}\";")
-    print("}")
+                if args.deptags:
+                    dot.write(f"\"{node.id}\" -> \"__dep_tag_{dep_tag_id}\" [ arrowhead=none ];\n")
+                    dot.write(f"\"__dep_tag_{dep_tag_id}\" -> \"{dep.id}\";\n")
+                    dot.write(f"\"__dep_tag_{dep_tag_id}\""
+                                  + f"[label={format_dep_label(node, dep)} shape=ellipse"
+                                  + " color=\"#666666\" fontcolor=\"#666666\"];\n")
+                else:
+                    dot.write(f"\"{node.id}\" -> \"{dep.id}\";\n")
+                dep_tag_id += 1
+    dot.write("}\n")
+    text = dot.getvalue()
+
+    # Write it somewhere
+    if args.dot:
+        with open(args.dot, "w") as f:
+            f.write(text)
+    elif args.svg:
+        subprocess.run(["dot", "-Tsvg", "-o", args.svg],
+                              input=text, text=True, check=True)
+    else:
+        sys.stdout.write(text)
 
 
-def get_deps(nodes, root):
+def get_deps(nodes, root, maxdepth, reverse):
     if root in nodes:
         return
     nodes.add(root)
-    for dep in root.deps:
-        get_deps(nodes, dep)
+    if maxdepth != 0:
+        for dep in (root.rdeps if reverse else root.deps):
+            get_deps(nodes, dep, maxdepth-1, reverse)
+
+
+def new_module_formatter(args):
+    def module_formatter(module):
+        if not args.label:
+            return []
+        result = []
+        text = json.dumps(module, default=lambda o: o.__dict__)
+        for jq_filter in args.label:
+            proc = subprocess.run(["jq", jq_filter],
+                                  input=text, text=True, check=True, stdout=subprocess.PIPE)
+            if proc.stdout:
+                o = json.loads(proc.stdout)
+                if type(o) == list:
+                    for row in o:
+                        if row:
+                            result.append(row)
+                elif type(o) == dict:
+                    result.append(str(proc.stdout).strip())
+                else:
+                    if o:
+                        result.append(str(o).strip())
+        return result
+    return module_formatter
 
 
 class BetweenCommand:
@@ -213,11 +297,13 @@
 
     def args(self, parser):
         parser.add_argument("module", nargs=2,
-                            help="The two modules")
+                            help="the two modules")
+        print_args(parser)
 
     def run(self, args):
         graph = load_graph()
-        print_nodes(graph.find_paths(args.module[0], args.module[1]))
+        print_nodes(args, graph.find_paths(args.module[0], args.module[1]),
+                    new_module_formatter(args))
 
 
 class DepsCommand:
@@ -226,21 +312,26 @@
     def args(self, parser):
         parser.add_argument("module", nargs="+",
                             help="Module to print dependencies of")
+        parser.add_argument("--reverse", action="store_true",
+                            help="traverse reverse dependencies")
+        parser.add_argument("--depth", type=int, default=-1,
+                            help="max depth of dependencies (can keep the graph size reasonable)")
+        print_args(parser)
 
     def run(self, args):
         graph = load_graph()
         nodes = set()
         err = False
-        for id in sys.argv[3:]:
+        for id in args.module:
             root = graph.nodes.get(id)
             if not root:
                 sys.stderr.write(f"error: Can't find root: {id}\n")
                 err = True
                 continue
-            get_deps(nodes, root)
+            get_deps(nodes, root, args.depth, args.reverse)
         if err:
             sys.exit(1)
-        print_nodes(nodes)
+        print_nodes(args, nodes, new_module_formatter(args))
 
 
 class IdCommand:
@@ -254,6 +345,25 @@
             print(m.id)
 
 
+class JsonCommand:
+    help = "Print metadata about modules in json format"
+
+    def args(self, parser):
+        module_selection_args(parser)
+        parser.add_argument("--list", action="store_true",
+                            help="Print the results in a json list. If not set and multiple"
+                            + " modules are matched, the output won't be valid json.")
+
+    def run(self, args):
+        modules = load_and_filter_modules(args)
+        if args.list:
+            json.dump([m for m in modules], sys.stdout, indent=4, default=lambda o: o.__dict__)
+        else:
+            for m in modules:
+                json.dump(m, sys.stdout, indent=4, default=lambda o: o.__dict__)
+                print()
+
+
 class QueryCommand:
     help = "Query details about modules"
 
@@ -275,6 +385,7 @@
     "between": BetweenCommand(),
     "deps": DepsCommand(),
     "id": IdCommand(),
+    "json": JsonCommand(),
     "query": QueryCommand(),
 }
 
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 43fb71d..b72b6d3 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -85,6 +85,9 @@
 func (singleton *sizesSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var deps android.Paths
 	ctx.VisitAllModules(func(m android.Module) {
+		if !m.ExportedToMake() {
+			return
+		}
 		filePaths, ok := android.SingletonModuleProvider(ctx, m, fileSizeMeasurerKey)
 		if !ok {
 			return
diff --git a/bpf/bpf.go b/bpf/bpf.go
index e1b512f..38fbd88 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -203,6 +203,15 @@
 		}
 
 	}
+
+	installDir := android.PathForModuleInstall(ctx, "etc", "bpf")
+	if len(bpf.properties.Sub_dir) > 0 {
+		installDir = installDir.Join(ctx, bpf.properties.Sub_dir)
+	}
+	for _, obj := range bpf.objs {
+		ctx.PackageFile(installDir, obj.Base(), obj)
+	}
+
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
 }
 
diff --git a/cc/afdo.go b/cc/afdo.go
index 79fbae1..00b2245 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -21,29 +21,17 @@
 	"android/soong/android"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/proptools"
 )
 
 // This flag needs to be in both CFlags and LdFlags to ensure correct symbol ordering
 const afdoFlagsFormat = "-fprofile-sample-use=%s -fprofile-sample-accurate"
 
-func recordMissingAfdoProfileFile(ctx android.BaseModuleContext, missing string) {
-	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
-}
-
-type afdoRdep struct {
-	VariationName *string
-	ProfilePath   *string
-}
-
 type AfdoProperties struct {
 	// Afdo allows developers self-service enroll for
 	// automatic feedback-directed optimization using profile data.
 	Afdo bool
 
-	FdoProfilePath *string `blueprint:"mutated"`
-
-	AfdoRDeps []afdoRdep `blueprint:"mutated"`
+	AfdoDep bool `blueprint:"mutated"`
 }
 
 type afdo struct {
@@ -62,13 +50,32 @@
 }
 
 // afdoEnabled returns true for binaries and shared libraries
-// that set afdo prop to True and there is a profile available
+// that set afdo prop to True.
 func (afdo *afdo) afdoEnabled() bool {
 	return afdo != nil && afdo.Properties.Afdo
 }
 
+func (afdo *afdo) isAfdoCompile(ctx ModuleContext) bool {
+	fdoProfilePath := getFdoProfilePathFromDep(ctx)
+	return !ctx.Host() && (afdo.Properties.Afdo || afdo.Properties.AfdoDep) && (fdoProfilePath != "")
+}
+
+func getFdoProfilePathFromDep(ctx ModuleContext) string {
+	fdoProfileDeps := ctx.GetDirectDepsWithTag(FdoProfileTag)
+	if len(fdoProfileDeps) > 0 && fdoProfileDeps[0] != nil {
+		if info, ok := android.OtherModuleProvider(ctx, fdoProfileDeps[0], FdoProfileProvider); ok {
+			return info.Path.String()
+		}
+	}
+	return ""
+}
+
 func (afdo *afdo) flags(ctx ModuleContext, flags Flags) Flags {
-	if afdo.Properties.Afdo {
+	if ctx.Host() {
+		return flags
+	}
+
+	if afdo.Properties.Afdo || afdo.Properties.AfdoDep {
 		// We use `-funique-internal-linkage-names` to associate profiles to the right internal
 		// functions. This option should be used before generating a profile. Because a profile
 		// generated for a binary without unique names doesn't work well building a binary with
@@ -86,15 +93,15 @@
 		// TODO(b/266595187): Remove the following feature once it is enabled in LLVM by default.
 		flags.Local.CFlags = append([]string{"-mllvm", "-improved-fs-discriminator=true"}, flags.Local.CFlags...)
 	}
-	if path := afdo.Properties.FdoProfilePath; path != nil {
+	if fdoProfilePath := getFdoProfilePathFromDep(ctx); fdoProfilePath != "" {
 		// The flags are prepended to allow overriding.
-		profileUseFlag := fmt.Sprintf(afdoFlagsFormat, *path)
+		profileUseFlag := fmt.Sprintf(afdoFlagsFormat, fdoProfilePath)
 		flags.Local.CFlags = append([]string{profileUseFlag}, flags.Local.CFlags...)
 		flags.Local.LdFlags = append([]string{profileUseFlag, "-Wl,-mllvm,-no-warn-sample-unused=true"}, flags.Local.LdFlags...)
 
 		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
 		// if profileFile gets updated
-		pathForSrc := android.PathForSource(ctx, *path)
+		pathForSrc := android.PathForSource(ctx, fdoProfilePath)
 		flags.CFlagsDeps = append(flags.CFlagsDeps, pathForSrc)
 		flags.LdFlagsDeps = append(flags.LdFlagsDeps, pathForSrc)
 	}
@@ -102,122 +109,86 @@
 	return flags
 }
 
-func (afdo *afdo) addDep(ctx BaseModuleContext, actx android.BottomUpMutatorContext) {
+func (a *afdo) addDep(ctx android.BottomUpMutatorContext, fdoProfileTarget string) {
+	if fdoProfileName, err := ctx.DeviceConfig().AfdoProfile(fdoProfileTarget); fdoProfileName != "" && err == nil {
+		ctx.AddFarVariationDependencies(
+			[]blueprint.Variation{
+				{Mutator: "arch", Variation: ctx.Target().ArchVariation()},
+				{Mutator: "os", Variation: "android"},
+			},
+			FdoProfileTag,
+			fdoProfileName)
+	}
+}
+
+func afdoPropagateViaDepTag(tag blueprint.DependencyTag) bool {
+	libTag, isLibTag := tag.(libraryDependencyTag)
+	// Do not recurse down non-static dependencies
+	if isLibTag {
+		return libTag.static()
+	} else {
+		return tag == objDepTag || tag == reuseObjTag || tag == staticVariantTag
+	}
+}
+
+// afdoTransitionMutator creates afdo variants of cc modules.
+type afdoTransitionMutator struct{}
+
+func (a *afdoTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	return []string{""}
+}
+
+func (a *afdoTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
 	if ctx.Host() {
-		return
+		return ""
 	}
 
-	if ctx.static() && !ctx.staticBinary() {
-		return
-	}
-
-	if c, ok := ctx.Module().(*Module); ok && c.Enabled() {
-		if fdoProfileName, err := actx.DeviceConfig().AfdoProfile(actx.ModuleName()); fdoProfileName != nil && err == nil {
-			actx.AddFarVariationDependencies(
-				[]blueprint.Variation{
-					{Mutator: "arch", Variation: actx.Target().ArchVariation()},
-					{Mutator: "os", Variation: "android"},
-				},
-				FdoProfileTag,
-				[]string{*fdoProfileName}...,
-			)
+	if m, ok := ctx.Module().(*Module); ok && m.afdo != nil {
+		if !afdoPropagateViaDepTag(ctx.DepTag()) {
+			return ""
 		}
+
+		if sourceVariation != "" {
+			return sourceVariation
+		}
+
+		if !m.afdo.afdoEnabled() {
+			return ""
+		}
+
+		// TODO(b/324141705): this is designed to prevent propagating AFDO from static libraries that have afdo: true set, but
+		//  it should be m.static() && !m.staticBinary() so that static binaries use AFDO variants of dependencies.
+		if m.static() {
+			return ""
+		}
+
+		return encodeTarget(ctx.Module().Name())
 	}
+	return ""
 }
 
-// FdoProfileMutator reads the FdoProfileProvider from a direct dep with FdoProfileTag
-// assigns FdoProfileInfo.Path to the FdoProfilePath mutated property
-func (c *Module) fdoProfileMutator(ctx android.BottomUpMutatorContext) {
-	if !c.Enabled() {
-		return
+func (a *afdoTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if m, ok := ctx.Module().(*Module); ok && m.afdo != nil {
+		return incomingVariation
 	}
-
-	if !c.afdo.afdoEnabled() {
-		return
-	}
-
-	ctx.VisitDirectDepsWithTag(FdoProfileTag, func(m android.Module) {
-		if info, ok := android.OtherModuleProvider(ctx, m, FdoProfileProvider); ok {
-			c.afdo.Properties.FdoProfilePath = proptools.StringPtr(info.Path.String())
-		}
-	})
+	return ""
 }
 
-var _ FdoProfileMutatorInterface = (*Module)(nil)
-
-// Propagate afdo requirements down from binaries and shared libraries
-func afdoDepsMutator(mctx android.TopDownMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.afdo.afdoEnabled() {
-		path := m.afdo.Properties.FdoProfilePath
-		mctx.WalkDeps(func(dep android.Module, parent android.Module) bool {
-			tag := mctx.OtherModuleDependencyTag(dep)
-			libTag, isLibTag := tag.(libraryDependencyTag)
-
-			// Do not recurse down non-static dependencies
-			if isLibTag {
-				if !libTag.static() {
-					return false
-				}
-			} else {
-				if tag != objDepTag && tag != reuseObjTag {
-					return false
-				}
+func (a *afdoTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if m, ok := ctx.Module().(*Module); ok && m.afdo != nil {
+		if variation == "" {
+			// The empty variation is either a module that has enabled AFDO for itself, or the non-AFDO
+			// variant of a dependency.
+			if m.afdo.afdoEnabled() && !(m.static() && !m.staticBinary()) && !m.Host() {
+				m.afdo.addDep(ctx, ctx.ModuleName())
 			}
-
-			if dep, ok := dep.(*Module); ok {
-				dep.afdo.Properties.AfdoRDeps = append(
-					dep.afdo.Properties.AfdoRDeps,
-					afdoRdep{
-						VariationName: proptools.StringPtr(encodeTarget(m.Name())),
-						ProfilePath:   path,
-					},
-				)
-			}
-
-			return true
-		})
-	}
-}
-
-// Create afdo variants for modules that need them
-func afdoMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.afdo != nil {
-		if !m.static() && m.afdo.Properties.Afdo {
-			mctx.SetDependencyVariation(encodeTarget(m.Name()))
-			return
-		}
-
-		variationNames := []string{""}
-
-		variantNameToProfilePath := make(map[string]*string)
-
-		for _, afdoRDep := range m.afdo.Properties.AfdoRDeps {
-			variantName := *afdoRDep.VariationName
-			// An rdep can be set twice in AfdoRDeps because there can be
-			// more than one path from an afdo-enabled module to
-			// a static dep such as
-			// afdo_enabled_foo -> static_bar ----> static_baz
-			//                   \                      ^
-			//                    ----------------------|
-			// We only need to create one variant per unique rdep
-			if _, exists := variantNameToProfilePath[variantName]; !exists {
-				variationNames = append(variationNames, variantName)
-				variantNameToProfilePath[variantName] = afdoRDep.ProfilePath
-			}
-		}
-
-		if len(variationNames) > 1 {
-			modules := mctx.CreateVariations(variationNames...)
-			for i, name := range variationNames {
-				if name == "" {
-					continue
-				}
-				variation := modules[i].(*Module)
-				variation.Properties.PreventInstall = true
-				variation.Properties.HideFromMake = true
-				variation.afdo.Properties.Afdo = true
-				variation.afdo.Properties.FdoProfilePath = variantNameToProfilePath[name]
-			}
+		} else {
+			// The non-empty variation is the AFDO variant of a dependency of a module that enabled AFDO
+			// for itself.
+			m.Properties.PreventInstall = true
+			m.Properties.HideFromMake = true
+			m.afdo.Properties.AfdoDep = true
+			m.afdo.addDep(ctx, decodeTarget(variation))
 		}
 	}
 }
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
index b250ad1..0679d13 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"runtime"
 	"strings"
 	"testing"
 
@@ -42,19 +43,25 @@
 	bp := `
 	cc_library_shared {
 		name: "libTest",
+		host_supported: true,
 		srcs: ["test.c"],
 		static_libs: ["libFoo"],
 		afdo: true,
+		lto: {
+			thin: true,
+		},
 	}
 
 	cc_library_static {
 		name: "libFoo",
+		host_supported: true,
 		srcs: ["foo.c"],
 		static_libs: ["libBar"],
 	}
 
 	cc_library_static {
 		name: "libBar",
+		host_supported: true,
 		srcs: ["bar.c"],
 	}
 	`
@@ -72,13 +79,20 @@
 			"afdo_profiles_package/Android.bp": []byte(`
 				fdo_profile {
 					name: "libTest_afdo",
-					profile: "libTest.afdo",
+					arch: {
+						arm64: {
+							profile: "libTest.afdo",
+						},
+					},
 				}
 			`),
 		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	expectedCFlag := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
+	profileSampleCFlag := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
+	uniqueInternalLinkageNamesCFlag := "-funique-internal-linkage-names"
+	afdoLtoLdFlag := "-Wl,-plugin-opt,-import-instr-limit=40"
+	noAfdoLtoLdFlag := "-Wl,-plugin-opt,-import-instr-limit=5"
 
 	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
 	libFooAfdoVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
@@ -86,18 +100,32 @@
 
 	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
-	if !strings.Contains(cFlags, expectedCFlag) {
-		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	if !strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected 'libTest' to enable afdo profile, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+
+	ldFlags := libTest.Rule("ld").Args["ldFlags"]
+	if !strings.Contains(ldFlags, afdoLtoLdFlag) {
+		t.Errorf("Expected 'libTest' to enable afdo, but did not find %q in ldflags %q", afdoLtoLdFlag, ldFlags)
 	}
 
 	cFlags = libFooAfdoVariant.Rule("cc").Args["cFlags"]
-	if !strings.Contains(cFlags, expectedCFlag) {
-		t.Errorf("Expected 'libFooAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	if !strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected 'libFooAfdoVariant' to enable afdo profile, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected 'libFooAfdoVariant' to enable afdo, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
 	}
 
 	cFlags = libBarAfdoVariant.Rule("cc").Args["cFlags"]
-	if !strings.Contains(cFlags, expectedCFlag) {
-		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", expectedCFlag, cFlags)
+	if !strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo profile, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected 'libBarAfdoVariant' to enable afdo, but did not find %q in cflags %q", profileSampleCFlag, cFlags)
 	}
 
 	// Check dependency edge from afdo-enabled module to static deps
@@ -114,12 +142,18 @@
 	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
-	if strings.Contains(cFlags, expectedCFlag) {
-		t.Errorf("Expected 'libFoo' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected 'libFoo' to not enable afdo profile, but found %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+	if strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected 'libFoo' to not enable afdo, but found %q in cflags %q", profileSampleCFlag, cFlags)
 	}
 	cFlags = libBar.Rule("cc").Args["cFlags"]
-	if strings.Contains(cFlags, expectedCFlag) {
-		t.Errorf("Expected 'libBar' to not enable afdo, but found %q in cflags %q", expectedCFlag, cFlags)
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected 'libBar' to not enable afdo profile, but found %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+	if strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected 'libBar' to not enable afdo, but found %q in cflags %q", profileSampleCFlag, cFlags)
 	}
 
 	// Check dependency edges of static deps
@@ -130,6 +164,104 @@
 	if !hasDirectDep(result, libFoo.Module(), libBar.Module()) {
 		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
+
+	// Verify that the arm variant does not have FDO since the fdo_profile module only has a profile for arm64
+	libTest32 := result.ModuleForTests("libTest", "android_arm_armv7-a-neon_shared")
+	libFooAfdoVariant32 := result.ModuleForTests("libFoo", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
+	libBarAfdoVariant32 := result.ModuleForTests("libBar", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
+
+	cFlags = libTest32.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected arm32 'libTest' not to enable afdo, but found %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+
+	// TODO(b/324141705): when the fdo_profile module doesn't provide a source file the dependencies don't get
+	//  -funique-internal-linkage-names but the module does.
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected arm32 'libTest' to enable -funique-internal-linkage-names but did not find %q in cflags %q",
+			uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+
+	ldFlags = libTest32.Rule("ld").Args["ldFlags"]
+	if !strings.Contains(ldFlags, noAfdoLtoLdFlag) {
+		t.Errorf("Expected arm32 'libTest' to not enable afdo, but did not find %q in ldflags %q", noAfdoLtoLdFlag, ldFlags)
+	}
+	if strings.Contains(ldFlags, afdoLtoLdFlag) {
+		t.Errorf("Expected arm32 'libTest' to not enable afdo, but found %q in ldflags %q", afdoLtoLdFlag, ldFlags)
+	}
+
+	// Check dependency edge from afdo-enabled module to static deps
+	if !hasDirectDep(result, libTest32.Module(), libFooAfdoVariant32.Module()) {
+		t.Errorf("arm32 libTest missing dependency on afdo variant of libFoo")
+	}
+
+	if !hasDirectDep(result, libFooAfdoVariant32.Module(), libBarAfdoVariant32.Module()) {
+		t.Errorf("arm32 libTest missing dependency on afdo variant of libBar")
+	}
+
+	cFlags = libFooAfdoVariant32.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected arm32 'libFoo' to not enable afdo profile, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected arm32 'libFoo' to enable afdo, but did not find %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	cFlags = libBarAfdoVariant32.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected arm32 'libBar' to not enable afdo profile, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	if !strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected arm32 'libBar' to enable afdo, but did not find %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+
+	// Verify that the host variants don't enable afdo
+	libTestHost := result.ModuleForTests("libTest", result.Config.BuildOSTarget.String()+"_shared")
+	libFooHost := result.ModuleForTests("libFoo", result.Config.BuildOSTarget.String()+"_static_lto-thin")
+	libBarHost := result.ModuleForTests("libBar", result.Config.BuildOSTarget.String()+"_static_lto-thin")
+
+	cFlags = libTestHost.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected host 'libTest' to not enable afdo profile, but found %q in cflags %q", profileSampleCFlag, cFlags)
+	}
+
+	if strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected host 'libTest' to not enable afdo but found %q in cflags %q",
+			uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+
+	if runtime.GOOS != "darwin" {
+		ldFlags := libTestHost.Rule("ld").Args["ldFlags"]
+		if !strings.Contains(ldFlags, noAfdoLtoLdFlag) {
+			t.Errorf("Expected host 'libTest' to not enable afdo, but did not find %q in ldflags %q", noAfdoLtoLdFlag, ldFlags)
+		}
+		if strings.Contains(ldFlags, afdoLtoLdFlag) {
+			t.Errorf("Expected host 'libTest' to not enable afdo, but found %q in ldflags %q", afdoLtoLdFlag, ldFlags)
+		}
+	}
+
+	// Check dependency edge from afdo-enabled module to static deps
+	if !hasDirectDep(result, libTestHost.Module(), libFooHost.Module()) {
+		t.Errorf("host libTest missing dependency on non-afdo variant of libFoo")
+	}
+
+	if !hasDirectDep(result, libFooHost.Module(), libBarHost.Module()) {
+		t.Errorf("host libTest missing dependency on non-afdo variant of libBar")
+	}
+
+	cFlags = libFooHost.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected host 'libFoo' to not enable afdo profile, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	if strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected host 'libFoo' to not enable afdo, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	cFlags = libBarHost.Rule("cc").Args["cFlags"]
+	if strings.Contains(cFlags, profileSampleCFlag) {
+		t.Errorf("Expected host 'libBar' to not enable afdo profile, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
+	if strings.Contains(cFlags, uniqueInternalLinkageNamesCFlag) {
+		t.Errorf("Expected host 'libBar' to not enable afdo, but found %q in cflags %q", uniqueInternalLinkageNamesCFlag, cFlags)
+	}
 }
 
 func TestAfdoEnabledOnStaticDepNoAfdo(t *testing.T) {
@@ -174,11 +306,11 @@
 	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static").Module()
 
 	if !hasDirectDep(result, libTest, libFoo.Module()) {
-		t.Errorf("libTest missing dependency on afdo variant of libFoo")
+		t.Errorf("libTest missing dependency on non-afdo variant of libFoo")
 	}
 
 	if !hasDirectDep(result, libFoo.Module(), libBar) {
-		t.Errorf("libFoo missing dependency on afdo variant of libBar")
+		t.Errorf("libFoo missing dependency on non-afdo variant of libBar")
 	}
 
 	fooVariants := result.ModuleVariantsForTests("foo")
diff --git a/cc/cc.go b/cc/cc.go
index 449d38f..0fa3457 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -55,7 +55,6 @@
 		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
 		ctx.BottomUp("version", versionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
-		ctx.BottomUp("fdo_profile", fdoProfileMutator)
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
@@ -70,8 +69,7 @@
 
 		ctx.Transition("coverage", &coverageTransitionMutator{})
 
-		ctx.TopDown("afdo_deps", afdoDepsMutator)
-		ctx.BottomUp("afdo", afdoMutator).Parallel()
+		ctx.Transition("afdo", &afdoTransitionMutator{})
 
 		ctx.Transition("orderfile", &orderfileTransitionMutator{})
 
@@ -527,7 +525,7 @@
 	selectedStl() string
 	baseModuleName() string
 	getVndkExtendsModuleName() string
-	isAfdoCompile() bool
+	isAfdoCompile(ctx ModuleContext) bool
 	isOrderfileCompile() bool
 	isCfi() bool
 	isFuzzer() bool
@@ -768,6 +766,12 @@
 
 var _ android.InstallNeededDependencyTag = libraryDependencyTag{}
 
+func (d libraryDependencyTag) PropagateAconfigValidation() bool {
+	return d.static()
+}
+
+var _ android.PropagateAconfigValidationDependencyTag = libraryDependencyTag{}
+
 // dependencyTag is used for tagging miscellaneous dependency types that don't fit into
 // libraryDependencyTag.  Each tag object is created globally and reused for multiple
 // dependencies (although since the object contains no references, assigning a tag to a
@@ -1381,9 +1385,9 @@
 	return false
 }
 
-func (c *Module) isAfdoCompile() bool {
+func (c *Module) isAfdoCompile(ctx ModuleContext) bool {
 	if afdo := c.afdo; afdo != nil {
-		return afdo.Properties.FdoProfilePath != nil
+		return afdo.isAfdoCompile(ctx)
 	}
 	return false
 }
@@ -1706,8 +1710,8 @@
 	return ctx.mod.IsVndk()
 }
 
-func (ctx *moduleContextImpl) isAfdoCompile() bool {
-	return ctx.mod.isAfdoCompile()
+func (ctx *moduleContextImpl) isAfdoCompile(mctx ModuleContext) bool {
+	return ctx.mod.isAfdoCompile(mctx)
 }
 
 func (ctx *moduleContextImpl) isOrderfileCompile() bool {
@@ -2351,10 +2355,6 @@
 	}
 	ctx.ctx = ctx
 
-	if !actx.Host() || !ctx.static() || ctx.staticBinary() {
-		c.afdo.addDep(ctx, actx)
-	}
-
 	c.begin(ctx)
 }
 
@@ -3367,7 +3367,7 @@
 					c.sabi.Properties.ReexportedIncludes, depExporterInfo.IncludeDirs.Strings()...)
 			}
 
-			makeLibName := MakeLibName(ctx, c, ccDep, depName) + libDepTag.makeSuffix
+			makeLibName := MakeLibName(ctx, c, ccDep, ccDep.BaseModuleName()) + libDepTag.makeSuffix
 			switch {
 			case libDepTag.header():
 				c.Properties.AndroidMkHeaderLibs = append(
@@ -3408,7 +3408,7 @@
 			switch depTag {
 			case runtimeDepTag:
 				c.Properties.AndroidMkRuntimeLibs = append(
-					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ctx, c, ccDep, depName)+libDepTag.makeSuffix)
+					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ctx, c, ccDep, ccDep.BaseModuleName())+libDepTag.makeSuffix)
 				// Record BaseLibName for snapshots.
 				c.Properties.SnapshotRuntimeLibs = append(c.Properties.SnapshotRuntimeLibs, BaseLibName(depName))
 			case objDepTag:
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 6cc500b..d1b728e 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2680,15 +2680,13 @@
 		}
 	}
 	expected := []string{
-		"android_vendor.29_arm64_armv8-a_shared_current",
 		"android_vendor.29_arm64_armv8-a_shared",
-		"android_vendor.29_arm_armv7-a-neon_shared_current",
 		"android_vendor.29_arm_armv7-a-neon_shared",
 	}
 	android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
 
 	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
-	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
+	android.AssertSame(t, "use Vendor API level for default stubs", "202404", params.Args["apiLevel"])
 
 	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
 		t.Helper()
diff --git a/cc/check.go b/cc/check.go
index 58ff5b2..32d1f06 100644
--- a/cc/check.go
+++ b/cc/check.go
@@ -45,6 +45,8 @@
 				ctx.PropertyErrorf(prop, "-Weverything is not allowed in Android.bp files.  "+
 					"Build with `m ANDROID_TEMPORARILY_ALLOW_WEVERYTHING=true` to experiment locally with -Weverything.")
 			}
+		} else if strings.HasPrefix(flag, "-target") || strings.HasPrefix(flag, "--target") {
+			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use the correct target soong rule.", flag)
 		} else if strings.Contains(flag, " ") {
 			args := strings.Split(flag, " ")
 			if args[0] == "-include" {
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index 82beb29..0de9e05 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -51,6 +51,7 @@
 	arm64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
 		"-Wl,-z,separate-code",
+		"-Wl,-z,separate-loadable-segments",
 	}
 
 	arm64Lldflags = arm64Ldflags
diff --git a/cc/config/darwin_host.go b/cc/config/darwin_host.go
index b789590..47c61b0 100644
--- a/cc/config/darwin_host.go
+++ b/cc/config/darwin_host.go
@@ -29,11 +29,6 @@
 		"-fPIC",
 		"-funwind-tables",
 
-		// Workaround differences in inttypes.h between host and target.
-		//See bug 12708004.
-		"-D__STDC_FORMAT_MACROS",
-		"-D__STDC_CONSTANT_MACROS",
-
 		"-isysroot ${macSdkRoot}",
 		"-mmacosx-version-min=${macMinVersion}",
 		"-DMACOSX_DEPLOYMENT_TARGET=${macMinVersion}",
diff --git a/cc/config/global.go b/cc/config/global.go
index c562614..b21d56c 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -254,7 +254,7 @@
 		"-Wno-pointer-to-int-cast",
 		"-Werror=fortify-source",
 		// http://b/315246135 temporarily disabled
-		"-Wno-error=unused-variable",
+		"-Wno-unused-variable",
 		// http://b/315250603 temporarily disabled
 		"-Wno-error=format",
 		// Disabled because it produces many false positives. http://b/323050926
@@ -367,8 +367,6 @@
 		"-Wno-gnu-offsetof-extensions",
 		// TODO: Enable this warning http://b/315245071
 		"-Wno-fortify-source",
-		"-Wno-tautological-negation-compare",
-		"-Wno-tautological-undefined-compare",
 	}
 
 	llvmNextExtraCommonGlobalCflags = []string{
diff --git a/cc/config/riscv64_device.go b/cc/config/riscv64_device.go
index deb922b..6a84fee 100644
--- a/cc/config/riscv64_device.go
+++ b/cc/config/riscv64_device.go
@@ -26,9 +26,7 @@
 		// Help catch common 32/64-bit errors.
 		"-Werror=implicit-function-declaration",
 		"-march=rv64gcv_zba_zbb_zbs",
-		// Equivalent to "-munaligned-access", but our clang doesn't have that yet.
-		"-Xclang -target-feature -Xclang +unaligned-scalar-mem",
-		"-Xclang -target-feature -Xclang +unaligned-vector-mem",
+		"-munaligned-access",
 		// Until https://gitlab.com/qemu-project/qemu/-/issues/1976 is fixed...
 		"-mno-implicit-float",
 		// (https://github.com/google/android-riscv64/issues/124)
@@ -40,9 +38,7 @@
 	riscv64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
 		"-march=rv64gcv_zba_zbb_zbs",
-		// Equivalent to "-munaligned-access", but our clang doesn't have that yet.
-		"-Xclang -target-feature -Xclang +unaligned-scalar-mem",
-		"-Xclang -target-feature -Xclang +unaligned-vector-mem",
+		"-munaligned-access",
 		// We should change the default for this in clang, but for now...
 		// (https://github.com/google/android-riscv64/issues/124)
 		"-Wl,-mllvm -Wl,-jump-is-expensive=false",
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index b97d511..12119a7 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -31,6 +31,7 @@
 
 	x86_64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
+		"-Wl,-z,separate-loadable-segments",
 	}
 
 	X86_64Lldflags = x86_64Ldflags
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index f497bf9..9bc54d6 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -30,11 +30,6 @@
 		"-D_FORTIFY_SOURCE=2",
 		"-fstack-protector",
 
-		// Workaround differences in inttypes.h between host and target.
-		//See bug 12708004.
-		"-D__STDC_FORMAT_MACROS",
-		"-D__STDC_CONSTANT_MACROS",
-
 		"--gcc-toolchain=${LinuxGccRoot}",
 		"-fstack-protector-strong",
 	}
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index 561c500..1e61b01 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -27,9 +27,8 @@
 		"-DWIN32_LEAN_AND_MEAN",
 		"-Wno-unused-parameter",
 
-		// Workaround differences in inttypes.h between host and target.
-		//See bug 12708004.
-		"-D__STDC_FORMAT_MACROS",
+		// Workaround differences in <stdint.h> between host and target.
+		// Context: http://b/12708004
 		"-D__STDC_CONSTANT_MACROS",
 
 		// Use C99-compliant printf functions (%zd).
diff --git a/cc/coverage.go b/cc/coverage.go
index 43f5e07..f6092e4 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -91,7 +91,7 @@
 }
 
 func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps {
-	if cov.Properties.NeedCoverageVariant {
+	if cov.Properties.NeedCoverageVariant && ctx.Device() {
 		ctx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
 		}, CoverageDepTag, getGcovProfileLibraryName(ctx))
@@ -184,19 +184,22 @@
 		if gcovCoverage {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "--coverage")
 
-			coverage := ctx.GetDirectDepWithTag(getGcovProfileLibraryName(ctx), CoverageDepTag).(*Module)
-			deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
-
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv")
+			if ctx.Device() {
+				coverage := ctx.GetDirectDepWithTag(getGcovProfileLibraryName(ctx), CoverageDepTag).(*Module)
+				deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
+				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,getenv")
+			}
 		} else if clangCoverage {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrFlag)
 			if EnableContinuousCoverage(ctx) {
 				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm=-runtime-counter-relocation")
 			}
 
-			coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), CoverageDepTag).(*Module)
-			deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,open")
+			if ctx.Device() {
+				coverage := ctx.GetDirectDepWithTag(getClangProfileLibraryName(ctx), CoverageDepTag).(*Module)
+				deps.WholeStaticLibs = append(deps.WholeStaticLibs, coverage.OutputFile().Path())
+				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--wrap,open")
+			}
 		}
 	}
 
@@ -204,7 +207,7 @@
 }
 
 func (cov *coverage) begin(ctx BaseModuleContext) {
-	if ctx.Host() {
+	if ctx.Host() && !ctx.Os().Linux() {
 		// TODO(dwillemsen): because of -nodefaultlibs, we must depend on libclang_rt.profile-*.a
 		// Just turn off for now.
 	} else {
diff --git a/cc/fdo_profile.go b/cc/fdo_profile.go
index 0893da5..1a33957 100644
--- a/cc/fdo_profile.go
+++ b/cc/fdo_profile.go
@@ -43,23 +43,10 @@
 }
 
 // FdoProfileProvider is used to provide path to an fdo profile
-var FdoProfileProvider = blueprint.NewMutatorProvider[FdoProfileInfo]("fdo_profile")
-
-// FdoProfileMutatorInterface is the interface implemented by fdo_profile module type
-// module types that can depend on an fdo_profile module
-type FdoProfileMutatorInterface interface {
-	// FdoProfileMutator eithers set or get FdoProfileProvider
-	fdoProfileMutator(ctx android.BottomUpMutatorContext)
-}
-
-var _ FdoProfileMutatorInterface = (*fdoProfile)(nil)
+var FdoProfileProvider = blueprint.NewProvider[FdoProfileInfo]()
 
 // GenerateAndroidBuildActions of fdo_profile does not have any build actions
-func (fp *fdoProfile) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
-
-// FdoProfileMutator sets FdoProfileProvider to fdo_profile module
-// or sets afdo.Properties.FdoProfilePath to path in FdoProfileProvider of the depended fdo_profile
-func (fp *fdoProfile) fdoProfileMutator(ctx android.BottomUpMutatorContext) {
+func (fp *fdoProfile) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if fp.properties.Profile != nil {
 		path := android.PathForModuleSrc(ctx, *fp.properties.Profile)
 		android.SetProvider(ctx, FdoProfileProvider, FdoProfileInfo{
@@ -68,14 +55,6 @@
 	}
 }
 
-// fdoProfileMutator calls the generic fdoProfileMutator function of fdoProfileMutator
-// which is implemented by cc and cc.FdoProfile
-func fdoProfileMutator(ctx android.BottomUpMutatorContext) {
-	if f, ok := ctx.Module().(FdoProfileMutatorInterface); ok {
-		f.fdoProfileMutator(ctx)
-	}
-}
-
 func FdoProfileFactory() android.Module {
 	m := &fdoProfile{}
 	m.AddProperties(&m.properties)
diff --git a/cc/library.go b/cc/library.go
index 4684d32..5607632 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -677,18 +677,16 @@
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
 	if ctx.IsLlndk() {
+		vendorApiLevel := ctx.Config().VendorApiLevel()
+		if vendorApiLevel == "" {
+			// TODO(b/321892570): Some tests relying on old fixtures which
+			// doesn't set vendorApiLevel. Needs to fix them.
+			vendorApiLevel = ctx.Config().PlatformSdkVersion().String()
+		}
 		// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
-		vndkVer := ctx.Module().(*Module).VndkVersion()
-		if !inList(vndkVer, ctx.Config().PlatformVersionActiveCodenames()) || vndkVer == "" {
-			// For non-enforcing devices, vndkVer is empty. Use "current" in that case, too.
-			vndkVer = "current"
-		}
-		if library.stubsVersion() != "" {
-			vndkVer = library.stubsVersion()
-		}
 		nativeAbiResult := parseNativeAbiDefinition(ctx,
 			String(library.Properties.Llndk.Symbol_file),
-			android.ApiLevelOrPanic(ctx, vndkVer), "--llndk")
+			android.ApiLevelOrPanic(ctx, vendorApiLevel), "--llndk")
 		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Llndk.Unversioned) {
 			library.versionScriptPath = android.OptionalPathForPath(
@@ -1354,38 +1352,28 @@
 		fileName+".lsdump")
 }
 
-func getRefAbiDumpDir(isNdk, isLlndk bool) string {
-	var dirName string
-	if isNdk {
-		dirName = "ndk"
-	} else if isLlndk {
-		dirName = "vndk"
-	} else {
-		dirName = "platform"
-	}
-	return filepath.Join("prebuilts", "abi-dumps", dirName)
-}
-
-func prevRefAbiDumpVersion(ctx ModuleContext, dumpDir string) int {
+// Return the previous and current SDK versions for cross-version ABI diff.
+func crossVersionAbiDiffSdkVersions(ctx ModuleContext, dumpDir string) (string, string) {
 	sdkVersionInt := ctx.Config().PlatformSdkVersion().FinalInt()
 	sdkVersionStr := ctx.Config().PlatformSdkVersion().String()
 
 	if ctx.Config().PlatformSdkFinal() {
-		return sdkVersionInt - 1
+		return strconv.Itoa(sdkVersionInt - 1), sdkVersionStr
 	} else {
 		// The platform SDK version can be upgraded before finalization while the corresponding abi dumps hasn't
 		// been generated. Thus the Cross-Version Check chooses PLATFORM_SDK_VERION - 1 as previous version.
 		// This situation could be identified by checking the existence of the PLATFORM_SDK_VERION dump directory.
 		versionedDumpDir := android.ExistentPathForSource(ctx, dumpDir, sdkVersionStr)
 		if versionedDumpDir.Valid() {
-			return sdkVersionInt
+			return sdkVersionStr, strconv.Itoa(sdkVersionInt + 1)
 		} else {
-			return sdkVersionInt - 1
+			return strconv.Itoa(sdkVersionInt - 1), sdkVersionStr
 		}
 	}
 }
 
-func currRefAbiDumpVersion(ctx ModuleContext) string {
+// Return the SDK version for same-version ABI diff.
+func currRefAbiDumpSdkVersion(ctx ModuleContext) string {
 	if ctx.Config().PlatformSdkFinal() {
 		// After sdk finalization, the ABI of the latest API level must be consistent with the source code,
 		// so choose PLATFORM_SDK_VERSION as the current version.
@@ -1435,17 +1423,17 @@
 }
 
 func (library *libraryDecorator) sameVersionAbiDiff(ctx android.ModuleContext, referenceDump android.Path,
-	baseName string, isLlndkOrNdk bool) {
+	baseName, nameExt string, isLlndkOrNdk bool) {
 
 	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
 	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName
 
-	library.sourceAbiDiff(ctx, referenceDump, baseName, "",
+	library.sourceAbiDiff(ctx, referenceDump, baseName, nameExt,
 		isLlndkOrNdk, false /* allowExtensions */, "current", errorMessage)
 }
 
 func (library *libraryDecorator) optInAbiDiff(ctx android.ModuleContext, referenceDump android.Path,
-	baseName, nameExt string, isLlndkOrNdk bool, refDumpDir string) {
+	baseName, nameExt string, refDumpDir string) {
 
 	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
 	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName + " -ref-dump-dir $$ANDROID_BUILD_TOP/" + refDumpDir
@@ -1455,7 +1443,7 @@
 	}
 
 	library.sourceAbiDiff(ctx, referenceDump, baseName, nameExt,
-		isLlndkOrNdk, false /* allowExtensions */, "current", errorMessage)
+		false /* isLlndkOrNdk */, false /* allowExtensions */, "current", errorMessage)
 }
 
 func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
@@ -1470,38 +1458,56 @@
 		}
 		exportedHeaderFlags := strings.Join(SourceAbiFlags, " ")
 		headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx)
-		// The logic must be consistent with classifySourceAbiDump.
-		isNdk := ctx.isNdk(ctx.Config())
-		isLlndk := ctx.isImplementationForLLNDKPublic()
-		currVersion := currRefAbiDumpVersion(ctx)
+		currSdkVersion := currRefAbiDumpSdkVersion(ctx)
+		currVendorVersion := ctx.Config().VendorApiLevel()
 		library.sAbiOutputFile = transformDumpToLinkedDump(ctx, objs.sAbiDumpFiles, soFile, fileName, exportedHeaderFlags,
 			android.OptionalPathForModuleSrc(ctx, library.symbolFileForAbiCheck(ctx)),
 			headerAbiChecker.Exclude_symbol_versions,
 			headerAbiChecker.Exclude_symbol_tags,
-			currVersion)
+			currSdkVersion)
 
 		for _, tag := range classifySourceAbiDump(ctx) {
-			addLsdumpPath(tag + ":" + library.sAbiOutputFile.String())
-		}
+			addLsdumpPath(string(tag) + ":" + library.sAbiOutputFile.String())
 
-		dumpDir := getRefAbiDumpDir(isNdk, isLlndk)
-		binderBitness := ctx.DeviceConfig().BinderBitness()
-		// Check against the previous version.
-		prevVersionInt := prevRefAbiDumpVersion(ctx, dumpDir)
-		prevVersion := strconv.Itoa(prevVersionInt)
-		prevDumpDir := filepath.Join(dumpDir, prevVersion, binderBitness)
-		prevDumpFile := getRefAbiDumpFile(ctx, prevDumpDir, fileName)
-		if prevDumpFile.Valid() {
-			library.crossVersionAbiDiff(ctx, prevDumpFile.Path(),
-				fileName, isLlndk || isNdk,
-				strconv.Itoa(prevVersionInt+1), prevVersion)
-		}
-		// Check against the current version.
-		currDumpDir := filepath.Join(dumpDir, currVersion, binderBitness)
-		currDumpFile := getRefAbiDumpFile(ctx, currDumpDir, fileName)
-		if currDumpFile.Valid() {
-			library.sameVersionAbiDiff(ctx, currDumpFile.Path(),
-				fileName, isLlndk || isNdk)
+			dumpDirName := tag.dirName()
+			if dumpDirName == "" {
+				continue
+			}
+			dumpDir := filepath.Join("prebuilts", "abi-dumps", dumpDirName)
+			isLlndk := (tag == llndkLsdumpTag)
+			isNdk := (tag == ndkLsdumpTag)
+			binderBitness := ctx.DeviceConfig().BinderBitness()
+			nameExt := ""
+			if isLlndk {
+				nameExt = "llndk"
+			}
+			// Check against the previous version.
+			var prevVersion, currVersion string
+			// If this release config does not define VendorApiLevel, fall back to the old policy.
+			if isLlndk && currVendorVersion != "" {
+				prevVersion = ctx.Config().PrevVendorApiLevel()
+				currVersion = currVendorVersion
+			} else {
+				prevVersion, currVersion = crossVersionAbiDiffSdkVersions(ctx, dumpDir)
+			}
+			prevDumpDir := filepath.Join(dumpDir, prevVersion, binderBitness)
+			prevDumpFile := getRefAbiDumpFile(ctx, prevDumpDir, fileName)
+			if prevDumpFile.Valid() {
+				library.crossVersionAbiDiff(ctx, prevDumpFile.Path(),
+					fileName, isLlndk || isNdk, currVersion, nameExt+prevVersion)
+			}
+			// Check against the current version.
+			if isLlndk && currVendorVersion != "" {
+				currVersion = currVendorVersion
+			} else {
+				currVersion = currSdkVersion
+			}
+			currDumpDir := filepath.Join(dumpDir, currVersion, binderBitness)
+			currDumpFile := getRefAbiDumpFile(ctx, currDumpDir, fileName)
+			if currDumpFile.Valid() {
+				library.sameVersionAbiDiff(ctx, currDumpFile.Path(),
+					fileName, nameExt, isLlndk || isNdk)
+			}
 		}
 		// Check against the opt-in reference dumps.
 		for i, optInDumpDir := range headerAbiChecker.Ref_dump_dirs {
@@ -1513,8 +1519,7 @@
 				continue
 			}
 			library.optInAbiDiff(ctx, optInDumpFile.Path(),
-				fileName, "opt"+strconv.Itoa(i), isLlndk || isNdk,
-				optInDumpDirPath.String())
+				fileName, "opt"+strconv.Itoa(i), optInDumpDirPath.String())
 		}
 	}
 }
@@ -1886,6 +1891,10 @@
 	if library.hasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
 		return library.Properties.Stubs.Symbol_file
 	}
+	// TODO(b/309880485): Distinguish platform, NDK, LLNDK, and APEX version scripts.
+	if library.baseLinker.Properties.Version_script != nil {
+		return library.baseLinker.Properties.Version_script
+	}
 	return nil
 }
 
@@ -1906,12 +1915,15 @@
 	}
 
 	if library.hasLLNDKStubs() && ctx.Module().(*Module).InVendorOrProduct() {
-		// LLNDK libraries only need a single stubs variant.
-		return []string{android.FutureApiLevel.String()}
+		// LLNDK libraries only need a single stubs variant (""), which is
+		// added automatically in createVersionVariations().
+		return nil
 	}
 
 	// Future API level is implicitly added if there isn't
-	return addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	versions := addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	normalizeVersions(ctx, versions)
+	return versions
 }
 
 func addCurrentVersionIfNotPresent(vers []string) []string {
@@ -2283,10 +2295,6 @@
 		return
 	}
 	versions := library.stubsVersions(mctx)
-	if len(versions) <= 0 {
-		return
-	}
-	normalizeVersions(mctx, versions)
 	if mctx.Failed() {
 		return
 	}
diff --git a/cc/lto.go b/cc/lto.go
index b2b4570..05fa8ee 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -100,7 +100,7 @@
 	lto.Properties.LtoEnabled = ltoEnabled
 }
 
-func (lto *lto) flags(ctx BaseModuleContext, flags Flags) Flags {
+func (lto *lto) flags(ctx ModuleContext, flags Flags) Flags {
 	// TODO(b/131771163): CFI and Fuzzer controls LTO flags by themselves.
 	// This has be checked late because these properties can be mutated.
 	if ctx.isCfi() || ctx.isFuzzer() {
@@ -139,7 +139,7 @@
 		// Reduce the inlining threshold for a better balance of binary size and
 		// performance.
 		if !ctx.Darwin() {
-			if ctx.isAfdoCompile() {
+			if ctx.isAfdoCompile(ctx) {
 				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=40")
 			} else {
 				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=5")
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 1e0bdf3..22f31d9 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -463,6 +463,98 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+    def test_integration_with_llndk(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_34 { # introduced=34
+                global:
+                    foo;
+                    bar; # llndk
+            };
+            VERSION_35 { # introduced=35
+                global:
+                    wiggle;
+                    waggle;
+                    waggle; # llndk=202404
+                    bubble; # llndk=202404
+                    duddle;
+                    duddle; # llndk=202504
+            } VERSION_34;
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        f.api = 202404
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file, f)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            void bar() {}
+            void waggle() {}
+            void bubble() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_34 {
+                global:
+                    foo;
+                    bar;
+            };
+            VERSION_35 {
+                global:
+                    waggle;
+                    bubble;
+            } VERSION_34;
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+    def test_integration_with_llndk_with_single_version_block(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            LIBANDROID {
+                global:
+                    foo; # introduced=34
+                    bar; # introduced=35
+                    bar; # llndk=202404
+                    baz; # introduced=35
+            };
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        f.api = 202404
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file, f)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            void bar() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            LIBANDROID {
+                global:
+                    foo;
+                    bar;
+            };
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
     def test_empty_stub(self) -> None:
         """Tests that empty stubs can be generated.
 
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 95fb7ed..71b7e43 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -595,6 +595,9 @@
 		libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
 		expectedDependency := ctx.ModuleForTests(tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
 		android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from %s to %s\n", libfoo.Name(), tc.expectedDependencyName), true, hasDep(ctx, libfoo, expectedDependency))
+		// check that LOCAL_SHARED_LIBRARIES contains libbar and not libbar.v<N>
+		entries := android.AndroidMkEntriesForTest(t, ctx, libfoo)[0]
+		android.AssertStringListContains(t, "Version should not be present in LOCAL_SHARED_LIBRARIES", entries.EntryMap["LOCAL_SHARED_LIBRARIES"], "libbar")
 
 		// check installation rules
 		// the selected soong module should be exported to make
@@ -603,7 +606,157 @@
 
 		// check LOCAL_MODULE of the selected module name
 		// the prebuilt should have the same LOCAL_MODULE when exported to make
-		entries := android.AndroidMkEntriesForTest(t, ctx, libbar)[0]
+		entries = android.AndroidMkEntriesForTest(t, ctx, libbar)[0]
 		android.AssertStringEquals(t, "unexpected LOCAL_MODULE", "libbar", entries.EntryMap["LOCAL_MODULE"][0])
 	}
 }
+
+// Setting prefer on multiple prebuilts is an error, unless one of them is also listed in apex_contributions
+func TestMultiplePrebuiltsPreferredUsingLegacyFlags(t *testing.T) {
+	bp := `
+		// an rdep
+		cc_library {
+			name: "libfoo",
+			shared_libs: ["libbar"],
+		}
+
+		// multiple variations of dep
+		// source
+		cc_library {
+			name: "libbar",
+		}
+		// prebuilt "v1"
+		cc_prebuilt_library_shared {
+			name: "libbar",
+			srcs: ["libbar.so"],
+			prefer: true,
+		}
+		// prebuilt "v2"
+		cc_prebuilt_library_shared {
+			name: "libbar.v2",
+			stem: "libbar",
+			source_module_name: "libbar",
+			srcs: ["libbar.so"],
+			prefer: true,
+		}
+
+		// selectors
+		apex_contributions {
+			name: "myapex_contributions",
+			contents: [%v],
+		}
+		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
+	}
+
+	testCases := []struct {
+		desc                   string
+		selectedDependencyName string
+		expectedDependencyName string
+		expectedErr            string
+	}{
+		{
+			desc:        "Multiple prebuilts have prefer: true",
+			expectedErr: "Multiple prebuilt modules prebuilt_libbar and prebuilt_libbar.v2 have been marked as preferred for this source module",
+		},
+		{
+			desc:                   "Multiple prebuilts have prefer: true. The prebuilt listed in apex_contributions wins.",
+			selectedDependencyName: `"prebuilt_libbar"`,
+			expectedDependencyName: "prebuilt_libbar",
+		},
+	}
+
+	for _, tc := range testCases {
+		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",
+				}
+			}),
+		)
+		if tc.expectedErr != "" {
+			preparer = preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(tc.expectedErr))
+		}
+
+		ctx := testPrebuilt(t, fmt.Sprintf(bp, tc.selectedDependencyName), map[string][]byte{
+			"libbar.so": nil,
+			"crtx.o":    nil,
+		}, preparer)
+		if tc.expectedErr != "" {
+			return // the fixture will assert that the excepted err has been raised
+		}
+		libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		expectedDependency := ctx.ModuleForTests(tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
+		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/sabi.go b/cc/sabi.go
index 4ca9f5c..af26726 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -26,6 +26,30 @@
 	lsdumpPathsLock sync.Mutex
 )
 
+type lsdumpTag string
+
+const (
+	llndkLsdumpTag    lsdumpTag = "LLNDK"
+	ndkLsdumpTag      lsdumpTag = "NDK"
+	platformLsdumpTag lsdumpTag = "PLATFORM"
+	productLsdumpTag  lsdumpTag = "PRODUCT"
+	vendorLsdumpTag   lsdumpTag = "VENDOR"
+)
+
+// Return the prebuilt ABI dump directory for a tag; an empty string for an opt-in dump.
+func (tag *lsdumpTag) dirName() string {
+	switch *tag {
+	case ndkLsdumpTag:
+		return "ndk"
+	case llndkLsdumpTag:
+		return "vndk"
+	case platformLsdumpTag:
+		return "platform"
+	default:
+		return ""
+	}
+}
+
 // Properties for ABI compatibility checker in Android.bp.
 type headerAbiCheckerProperties struct {
 	// Enable ABI checks (even if this is not an LLNDK/VNDK lib)
@@ -98,8 +122,8 @@
 }
 
 // Returns a slice of strings that represent the ABI dumps generated for this module.
-func classifySourceAbiDump(ctx android.BaseModuleContext) []string {
-	result := []string{}
+func classifySourceAbiDump(ctx android.BaseModuleContext) []lsdumpTag {
+	result := []lsdumpTag{}
 	m := ctx.Module().(*Module)
 	headerAbiChecker := m.library.getHeaderAbiCheckerProperties(ctx)
 	if headerAbiChecker.explicitlyDisabled() {
@@ -107,21 +131,21 @@
 	}
 	if !m.InProduct() && !m.InVendor() {
 		if m.isImplementationForLLNDKPublic() {
-			result = append(result, "LLNDK")
+			result = append(result, llndkLsdumpTag)
 		}
 		// Return NDK if the library is both NDK and APEX.
 		// TODO(b/309880485): Split NDK and APEX ABI.
 		if m.IsNdk(ctx.Config()) {
-			result = append(result, "NDK")
+			result = append(result, ndkLsdumpTag)
 		} else if m.library.hasStubsVariants() || headerAbiChecker.enabled() {
-			result = append(result, "PLATFORM")
+			result = append(result, platformLsdumpTag)
 		}
 	} else if headerAbiChecker.enabled() {
 		if m.InProduct() {
-			result = append(result, "PRODUCT")
+			result = append(result, productLsdumpTag)
 		}
 		if m.InVendor() {
-			result = append(result, "VENDOR")
+			result = append(result, vendorLsdumpTag)
 		}
 	}
 	return result
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/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 345e9f9..4553616 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -103,13 +103,24 @@
     @property
     def has_llndk_tags(self) -> bool:
         """Returns True if any LL-NDK tags are set."""
-        return 'llndk' in self.tags
+        for tag in self.tags:
+            if tag == 'llndk' or tag.startswith('llndk='):
+                return True
+        return False
 
     @property
     def has_platform_only_tags(self) -> bool:
         """Returns True if any platform-only tags are set."""
         return 'platform-only' in self.tags
 
+    def copy_introduced_from(self, tags: Tags) -> None:
+        """Copies introduced= or introduced-*= tags."""
+        for tag in tags:
+            if tag.startswith('introduced=') or tag.startswith('introduced-'):
+                name, _ = split_tag(tag)
+                if not any(self_tag.startswith(name + '=') for self_tag in self.tags):
+                    self.tags += (tag,)
+
 
 @dataclass
 class Symbol:
@@ -147,6 +158,8 @@
     """Returns true if this tag has an API level that may need decoding."""
     if tag.startswith('llndk-deprecated='):
         return True
+    if tag.startswith('llndk='):
+        return True
     if tag.startswith('introduced='):
         return True
     if tag.startswith('introduced-'):
@@ -237,15 +250,22 @@
 
         This defines the rules shared between version tagging and symbol tagging.
         """
-        # The apex and llndk tags will only exclude APIs from other modes. If in
+        # LLNDK mode/tags follow the similar filtering except that API level checking
+        # is based llndk= instead of introduced=.
+        if self.llndk:
+            if tags.has_mode_tags and not tags.has_llndk_tags:
+                return True
+            if not symbol_in_arch(tags, self.arch):
+                return True
+            if not symbol_in_llndk_api(tags, self.arch, self.api):
+                return True
+            return False
         # APEX or LLNDK mode and neither tag is provided, we fall back to the
         # default behavior because all NDK symbols are implicitly available to
         # APEX and LLNDK.
         if tags.has_mode_tags:
             if self.apex and tags.has_apex_tags:
                 return False
-            if self.llndk and tags.has_llndk_tags:
-                return False
             if self.systemapi and tags.has_systemapi_tags:
                 return False
             return True
@@ -266,6 +286,10 @@
             return True
         if version.tags.has_platform_only_tags:
             return True
+        # Include all versions when targeting LLNDK because LLNDK symbols are self-versioned.
+        # Empty version block will be handled separately.
+        if self.llndk:
+            return False
         return self._should_omit_tags(version.tags)
 
     def should_omit_symbol(self, symbol: Symbol) -> bool:
@@ -292,6 +316,14 @@
     # for the tagged architectures.
     return not has_arch_tags
 
+def symbol_in_llndk_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
+    """Returns true if the symbol is present for the given LLNDK API level."""
+    # Check llndk= first.
+    for tag in tags:
+        if tag.startswith('llndk='):
+            return api >= int(get_tag_value(tag))
+    # If not, we keep old behavior: NDK symbols in <= 34 are LLNDK symbols.
+    return symbol_in_api(tags, arch, 34)
 
 def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
     """Returns true if the symbol is present for the given API level."""
@@ -368,6 +400,7 @@
                     f'Unexpected contents at top level: {self.current_line}')
 
         self.check_no_duplicate_symbols(versions)
+        self.check_llndk_introduced(versions)
         return versions
 
     def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
@@ -396,6 +429,31 @@
             raise MultiplyDefinedSymbolError(
                 sorted(list(multiply_defined_symbols)))
 
+    def check_llndk_introduced(self, versions: Iterable[Version]) -> None:
+        """Raises errors when llndk= is missing for new llndk symbols."""
+        if not self.filter.llndk:
+            return
+
+        def assert_llndk_with_version(tags: Tags,  name: str) -> None:
+            has_llndk_introduced = False
+            for tag in tags:
+                if tag.startswith('llndk='):
+                    has_llndk_introduced = True
+                    break
+            if not has_llndk_introduced:
+                raise ParseError(f'{name}: missing version. `llndk=yyyymm`')
+
+        arch = self.filter.arch
+        for version in versions:
+            # llndk symbols >= introduced=35 should be tagged
+            # explicitly with llndk=yyyymm.
+            for symbol in version.symbols:
+                if not symbol.tags.has_llndk_tags:
+                    continue
+                if symbol_in_api(symbol.tags, arch, 34):
+                    continue
+                assert_llndk_with_version(symbol.tags, symbol.name)
+
     def parse_version(self) -> Version:
         """Parses a single version section and returns a Version object."""
         assert self.current_line is not None
@@ -429,7 +487,9 @@
                 else:
                     raise ParseError('Unknown visiblity label: ' + visibility)
             elif global_scope and not cpp_symbols:
-                symbols.append(self.parse_symbol())
+                symbol = self.parse_symbol()
+                symbol.tags.copy_introduced_from(tags)
+                symbols.append(symbol)
             else:
                 # We're in a hidden scope or in 'extern "C++"' block. Ignore
                 # everything.
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index 83becc2..8b412b9 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -344,6 +344,45 @@
         self.assertInclude(f_llndk, s_none)
         self.assertInclude(f_llndk, s_llndk)
 
+    def test_omit_llndk_versioned(self) -> None:
+        f_ndk = self.filter
+        f_ndk.api = 35
+
+        f_llndk = copy(f_ndk)
+        f_llndk.llndk = True
+        f_llndk.api = 202404
+
+        s = Symbol('foo', Tags())
+        s_llndk = Symbol('foo', Tags.from_strs(['llndk']))
+        s_llndk_202404 = Symbol('foo', Tags.from_strs(['llndk=202404']))
+        s_34 = Symbol('foo', Tags.from_strs(['introduced=34']))
+        s_34_llndk = Symbol('foo', Tags.from_strs(['introduced=34', 'llndk']))
+        s_35 = Symbol('foo', Tags.from_strs(['introduced=35']))
+        s_35_llndk_202404 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202404']))
+        s_35_llndk_202504 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202504']))
+
+        # When targeting NDK, omit LLNDK tags
+        self.assertInclude(f_ndk, s)
+        self.assertOmit(f_ndk, s_llndk)
+        self.assertOmit(f_ndk, s_llndk_202404)
+        self.assertInclude(f_ndk, s_34)
+        self.assertOmit(f_ndk, s_34_llndk)
+        self.assertInclude(f_ndk, s_35)
+        self.assertOmit(f_ndk, s_35_llndk_202404)
+        self.assertOmit(f_ndk, s_35_llndk_202504)
+
+        # When targeting LLNDK, old symbols without any mode tags are included as LLNDK
+        self.assertInclude(f_llndk, s)
+        # When targeting LLNDK, old symbols with #llndk are included as LLNDK
+        self.assertInclude(f_llndk, s_llndk)
+        self.assertInclude(f_llndk, s_llndk_202404)
+        self.assertInclude(f_llndk, s_34)
+        self.assertInclude(f_llndk, s_34_llndk)
+        # When targeting LLNDK, new symbols(>=35) should be tagged with llndk-introduced=.
+        self.assertOmit(f_llndk, s_35)
+        self.assertInclude(f_llndk, s_35_llndk_202404)
+        self.assertOmit(f_llndk, s_35_llndk_202504)
+
     def test_omit_apex(self) -> None:
         f_none = self.filter
         f_apex = copy(f_none)
@@ -451,9 +490,12 @@
         self.assertIsNone(version.base)
         self.assertEqual(Tags.from_strs(['weak', 'introduced=35']), version.tags)
 
+        # Inherit introduced= tags from version block so that
+        # should_omit_tags() can differently based on introduced API level when treating
+        # LLNDK-available symbols.
         expected_symbols = [
-            Symbol('baz', Tags()),
-            Symbol('qux', Tags.from_strs(['apex', 'llndk'])),
+            Symbol('baz', Tags.from_strs(['introduced=35'])),
+            Symbol('qux', Tags.from_strs(['apex', 'llndk', 'introduced=35'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -601,6 +643,19 @@
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
+    def test_parse_llndk_version_is_missing(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 { # introduced=35
+                foo;
+                bar; # llndk
+            };
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse()
+
 
 def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 1aa6f6f..2c57180 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -342,7 +342,8 @@
 func (oz *OutputZip) getUninitializedPythonPackages(inputZips []InputZip) ([]string, error) {
 	// the runfiles packages needs to be populated with "__init__.py".
 	// the runfiles dirs have been treated as packages.
-	allPackages := make(map[string]bool)
+	var allPackages []string // Using a slice to preserve input order.
+	seenPkgs := make(map[string]bool)
 	initedPackages := make(map[string]bool)
 	getPackage := func(path string) string {
 		ret := filepath.Dir(path)
@@ -369,16 +370,17 @@
 				initedPackages[pyPkg] = true
 			}
 			for pyPkg != "" {
-				if _, found := allPackages[pyPkg]; found {
+				if _, found := seenPkgs[pyPkg]; found {
 					break
 				}
-				allPackages[pyPkg] = true
+				seenPkgs[pyPkg] = true
+				allPackages = append(allPackages, pyPkg)
 				pyPkg = getPackage(pyPkg)
 			}
 		}
 	}
 	noInitPackages := make([]string, 0)
-	for pyPkg := range allPackages {
+	for _, pyPkg := range allPackages {
 		if _, found := initedPackages[pyPkg]; !found {
 			noInitPackages = append(noInitPackages, pyPkg)
 		}
diff --git a/cmd/merge_zips/merge_zips_test.go b/cmd/merge_zips/merge_zips_test.go
index 64b08d0..17228c4 100644
--- a/cmd/merge_zips/merge_zips_test.go
+++ b/cmd/merge_zips/merge_zips_test.go
@@ -103,6 +103,7 @@
 		stripFiles       []string
 		stripDirs        []string
 		jar              bool
+		par              bool
 		sort             bool
 		ignoreDuplicates bool
 		stripDirEntries  bool
@@ -265,6 +266,34 @@
 			},
 			out: []testZipEntry{withoutTimestamp, a},
 		},
+		{
+			name: "emulate par",
+			in: [][]testZipEntry{
+				{
+					testZipEntry{name: "3/main.py"},
+					testZipEntry{name: "c/main.py"},
+					testZipEntry{name: "a/main.py"},
+					testZipEntry{name: "2/main.py"},
+					testZipEntry{name: "b/main.py"},
+					testZipEntry{name: "1/main.py"},
+				},
+			},
+			out: []testZipEntry{
+				testZipEntry{name: "3/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "c/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "a/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "2/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "b/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "1/__init__.py", mode: 0700, timestamp: jar.DefaultTime},
+				testZipEntry{name: "3/main.py", timestamp: jar.DefaultTime},
+				testZipEntry{name: "c/main.py", timestamp: jar.DefaultTime},
+				testZipEntry{name: "a/main.py", timestamp: jar.DefaultTime},
+				testZipEntry{name: "2/main.py", timestamp: jar.DefaultTime},
+				testZipEntry{name: "b/main.py", timestamp: jar.DefaultTime},
+				testZipEntry{name: "1/main.py", timestamp: jar.DefaultTime},
+			},
+			par: true,
+		},
 	}
 
 	for _, test := range testCases {
@@ -280,7 +309,7 @@
 			writer := zip.NewWriter(out)
 
 			err := mergeZips(inputZips, writer, "", "",
-				test.sort, test.jar, false, test.stripDirEntries, test.ignoreDuplicates,
+				test.sort, test.jar, test.par, test.stripDirEntries, test.ignoreDuplicates,
 				test.stripFiles, test.stripDirs, test.zipsToNotStrip)
 
 			closeErr := writer.Close()
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/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 5c2316a..eafd67a 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -22,7 +22,6 @@
 
 	"android/soong/android"
 	"android/soong/bp2build"
-	"android/soong/starlark_import"
 )
 
 // A helper function to generate a Read-only Bazel workspace in outDir
@@ -47,14 +46,6 @@
 		}
 	}
 
-	// Add starlark deps here, so that they apply to both queryview and apibp2build which
-	// both run this function.
-	starlarkDeps, err2 := starlark_import.GetNinjaDeps()
-	if err2 != nil {
-		return err2
-	}
-	ctx.AddNinjaFileDeps(starlarkDeps...)
-
 	return nil
 }
 
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 07d57c9..cdf6682 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -9,6 +9,7 @@
         "blueprint",
         "soong",
         "soong-android",
+        "soong-bpf", // for testing
         "soong-linkerconfig",
     ],
     srcs: [
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 2f6476c..64a2e23 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"io"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"android/soong/android"
@@ -50,8 +51,8 @@
 	// Function that builds extra files under the root directory and returns the files
 	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
 
-	// Function that filters PackagingSpecs returned by PackagingBase.GatherPackagingSpecs()
-	filterPackagingSpecs func(specs map[string]android.PackagingSpec)
+	// Function that filters PackagingSpec in PackagingBase.GatherPackagingSpecs()
+	filterPackagingSpec func(spec android.PackagingSpec) bool
 
 	output     android.OutputPath
 	installDir android.InstallPath
@@ -109,6 +110,12 @@
 
 	// Mount point for this image. Default is "/"
 	Mount_point *string
+
+	// If set to the name of a partition ("system", "vendor", etc), this filesystem module
+	// will also include the contents of the make-built staging directories. If any soong
+	// modules would be installed to the same location as a make module, they will overwrite
+	// the make version.
+	Include_make_built_files string
 }
 
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
@@ -183,13 +190,9 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 }
 
-// root zip will contain extra files/dirs that are not from the `deps` property.
-func (f *filesystem) buildRootZip(ctx android.ModuleContext) android.OutputPath {
-	rootDir := android.PathForModuleGen(ctx, "root").OutputPath
-	builder := android.NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("rm -rf").Text(rootDir.String())
-	builder.Command().Text("mkdir -p").Text(rootDir.String())
-
+// Copy extra files/dirs that are not from the `deps` property to `rootDir`, checking for conflicts with files
+// already in `rootDir`.
+func (f *filesystem) buildNonDepsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.OutputPath) {
 	// create dirs and symlinks
 	for _, dir := range f.properties.Dirs {
 		// OutputPath.Join verifies dir
@@ -212,65 +215,43 @@
 
 		// OutputPath.Join verifies name. don't need to verify target.
 		dst := rootDir.Join(ctx, name)
-
+		builder.Command().Textf("(! [ -e %s -o -L %s ] || (echo \"%s already exists from an earlier stage of the build\" && exit 1))", dst, dst, dst)
 		builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String()))
 		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
 	}
 
 	// create extra files if there's any
-	rootForExtraFiles := android.PathForModuleGen(ctx, "root-extra").OutputPath
-	var extraFiles android.OutputPaths
 	if f.buildExtraFiles != nil {
-		extraFiles = f.buildExtraFiles(ctx, rootForExtraFiles)
+		rootForExtraFiles := android.PathForModuleGen(ctx, "root-extra").OutputPath
+		extraFiles := f.buildExtraFiles(ctx, rootForExtraFiles)
 		for _, f := range extraFiles {
-			rel, _ := filepath.Rel(rootForExtraFiles.String(), f.String())
-			if strings.HasPrefix(rel, "..") {
-				panic(fmt.Errorf("%q is not under %q\n", f, rootForExtraFiles))
+			rel, err := filepath.Rel(rootForExtraFiles.String(), f.String())
+			if err != nil || strings.HasPrefix(rel, "..") {
+				ctx.ModuleErrorf("can't make %q relative to %q", f, rootForExtraFiles)
 			}
 		}
-	}
-
-	// Zip them all
-	zipOut := android.PathForModuleGen(ctx, "root.zip").OutputPath
-	zipCommand := builder.Command().BuiltTool("soong_zip")
-	zipCommand.FlagWithOutput("-o ", zipOut).
-		FlagWithArg("-C ", rootDir.String()).
-		Flag("-L 0"). // no compression because this will be unzipped soon
-		FlagWithArg("-D ", rootDir.String()).
-		Flag("-d") // include empty directories
-	if len(extraFiles) > 0 {
-		zipCommand.FlagWithArg("-C ", rootForExtraFiles.String())
-		for _, f := range extraFiles {
-			zipCommand.FlagWithInput("-f ", f)
+		if len(extraFiles) > 0 {
+			builder.Command().BuiltTool("merge_directories").
+				Implicits(extraFiles.Paths()).
+				Text(rootDir.String()).
+				Text(rootForExtraFiles.String())
 		}
 	}
-
-	builder.Command().Text("rm -rf").Text(rootDir.String())
-
-	builder.Build("zip_root", fmt.Sprintf("zipping root contents for %s", ctx.ModuleName()))
-	return zipOut
 }
 
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
-	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
-
-	builder := android.NewRuleBuilder(pctx, ctx)
-	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
-	rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath
-	builder.Command().
-		BuiltTool("zip2zip").
-		FlagWithInput("-i ", depsZipFile).
-		FlagWithOutput("-o ", rebasedDepsZip).
-		Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase
-
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	rootZip := f.buildRootZip(ctx)
-	builder.Command().
-		BuiltTool("zipsync").
-		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
-		Input(rootZip).
-		Input(rebasedDepsZip)
+	rebasedDir := rootDir
+	if f.properties.Base_dir != nil {
+		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
+	}
+	builder := android.NewRuleBuilder(pctx, ctx)
+	// Wipe the root dir to get rid of leftover files from prior builds
+	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
+	f.entries = f.CopySpecsToDir(ctx, builder, f.gatherFilteredPackagingSpecs(ctx), rebasedDir)
+
+	f.buildNonDepsFiles(ctx, builder, rootDir)
+	f.addMakeBuiltFiles(ctx, builder, rootDir)
 
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
@@ -311,18 +292,16 @@
 }
 
 func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
-	type prop struct {
-		name  string
-		value string
-	}
-
-	var props []prop
 	var deps android.Paths
+	var propFileString strings.Builder
 	addStr := func(name string, value string) {
-		props = append(props, prop{name, value})
+		propFileString.WriteString(name)
+		propFileString.WriteRune('=')
+		propFileString.WriteString(value)
+		propFileString.WriteRune('\n')
 	}
 	addPath := func(name string, path android.Path) {
-		props = append(props, prop{name, path.String()})
+		addStr(name, path.String())
 		deps = append(deps, path)
 	}
 
@@ -376,15 +355,7 @@
 		addStr("hash_seed", uuid)
 	}
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
-	builder := android.NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("rm").Flag("-rf").Output(propFile)
-	for _, p := range props {
-		builder.Command().
-			Text("echo").
-			Flag(`"` + p.name + "=" + p.value + `"`).
-			Text(">>").Output(propFile)
-	}
-	builder.Build("build_filesystem_prop", fmt.Sprintf("Creating filesystem props for %s", f.BaseModuleName()))
+	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
 	return propFile, deps
 }
 
@@ -398,25 +369,21 @@
 		ctx.PropertyErrorf("file_contexts", "file_contexts is not supported for compressed cpio image.")
 	}
 
-	depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath
-	f.entries = f.CopyDepsToZip(ctx, f.gatherFilteredPackagingSpecs(ctx), depsZipFile)
-
-	builder := android.NewRuleBuilder(pctx, ctx)
-	depsBase := proptools.StringDefault(f.properties.Base_dir, ".")
-	rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath
-	builder.Command().
-		BuiltTool("zip2zip").
-		FlagWithInput("-i ", depsZipFile).
-		FlagWithOutput("-o ", rebasedDepsZip).
-		Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase
+	if f.properties.Include_make_built_files != "" {
+		ctx.PropertyErrorf("include_make_built_files", "include_make_built_files is not supported for compressed cpio image.")
+	}
 
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	rootZip := f.buildRootZip(ctx)
-	builder.Command().
-		BuiltTool("zipsync").
-		FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear.
-		Input(rootZip).
-		Input(rebasedDepsZip)
+	rebasedDir := rootDir
+	if f.properties.Base_dir != nil {
+		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
+	}
+	builder := android.NewRuleBuilder(pctx, ctx)
+	// Wipe the root dir to get rid of leftover files from prior builds
+	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
+	f.entries = f.CopySpecsToDir(ctx, builder, f.gatherFilteredPackagingSpecs(ctx), rebasedDir)
+
+	f.buildNonDepsFiles(ctx, builder, rootDir)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	cmd := builder.Command().
@@ -439,6 +406,41 @@
 	return output
 }
 
+var validPartitions = []string{
+	"system",
+	"userdata",
+	"cache",
+	"system_other",
+	"vendor",
+	"product",
+	"system_ext",
+	"odm",
+	"vendor_dlkm",
+	"odm_dlkm",
+	"system_dlkm",
+}
+
+func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
+	partition := f.properties.Include_make_built_files
+	if partition == "" {
+		return
+	}
+	if !slices.Contains(validPartitions, partition) {
+		ctx.PropertyErrorf("include_make_built_files", "Expected one of %#v, found %q", validPartitions, partition)
+		return
+	}
+	stampFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/staging_dir.stamp", ctx.Config().DeviceName(), partition)
+	fileListFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partition)
+	stagingDir := fmt.Sprintf("target/product/%s/%s", ctx.Config().DeviceName(), partition)
+
+	builder.Command().BuiltTool("merge_directories").
+		Implicit(android.PathForArbitraryOutput(ctx, stampFile)).
+		Text("--ignore-duplicates").
+		FlagWithInput("--file-list", android.PathForArbitraryOutput(ctx, fileListFile)).
+		Text(rootDir.String()).
+		Text(android.PathForArbitraryOutput(ctx, stagingDir).String())
+}
+
 var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
 
 // Implements android.AndroidMkEntriesProvider
@@ -493,10 +495,7 @@
 // Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
 func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
-	specs := f.PackagingBase.GatherPackagingSpecs(ctx)
-	if f.filterPackagingSpecs != nil {
-		f.filterPackagingSpecs(specs)
-	}
+	specs := f.PackagingBase.GatherPackagingSpecsWithFilter(ctx, f.filterPackagingSpec)
 	return specs
 }
 
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index aef4756..6d62746 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -16,9 +16,11 @@
 
 import (
 	"os"
+	"path/filepath"
 	"testing"
 
 	"android/soong/android"
+	"android/soong/bpf"
 	"android/soong/cc"
 	"android/soong/etc"
 
@@ -31,6 +33,7 @@
 
 var fixture = android.GroupFixturePreparers(
 	android.PrepareForIntegrationTestWithAndroid,
+	bpf.PrepareForTestWithBpf,
 	etc.PrepareForTestWithPrebuiltEtc,
 	cc.PrepareForIntegrationTestWithCc,
 	PrepareForTestWithFilesystemBuildComponents,
@@ -40,11 +43,71 @@
 	result := fixture.RunTestWithBp(t, `
 		android_filesystem {
 			name: "myfilesystem",
+			multilib: {
+				common: {
+					deps: [
+						"bpf.o",
+					],
+				},
+				lib32: {
+					deps: [
+						"foo",
+						"libbar",
+					],
+				},
+				lib64: {
+					deps: [
+						"libbar",
+					],
+				},
+			},
+			compile_multilib: "both",
+		}
+
+		bpf {
+			name: "bpf.o",
+			srcs: ["bpf.c"],
+		}
+
+		cc_binary {
+			name: "foo",
+			compile_multilib: "prefer32",
+		}
+
+		cc_library {
+			name: "libbar",
 		}
 	`)
 
 	// produces "myfilesystem.img"
 	result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
+
+	fs := result.ModuleForTests("myfilesystem", "android_common").Module().(*filesystem)
+	expected := []string{
+		"bin/foo",
+		"lib/libbar.so",
+		"lib64/libbar.so",
+		"etc/bpf/bpf.o",
+	}
+	for _, e := range expected {
+		android.AssertStringListContains(t, "missing entry", fs.entries, e)
+	}
+}
+
+func TestIncludeMakeBuiltFiles(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "myfilesystem",
+			include_make_built_files: "system",
+		}
+	`)
+
+	output := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
+
+	stampFile := filepath.Join(result.Config.OutDir(), "target/product/test_device/obj/PACKAGING/system_intermediates/staging_dir.stamp")
+	fileListFile := filepath.Join(result.Config.OutDir(), "target/product/test_device/obj/PACKAGING/system_intermediates/file_list.txt")
+	android.AssertStringListContains(t, "deps of filesystem must include the staging dir stamp file", output.Implicits.Strings(), stampFile)
+	android.AssertStringListContains(t, "deps of filesystem must include the staging dir file list", output.Implicits.Strings(), fileListFile)
 }
 
 func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
@@ -224,7 +287,7 @@
 		}
 	`)
 
-	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("deps.zip").Implicits
+	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img").Implicits
 	android.AssertStringListContains(t, "filesystem should have libbar even for unbundled build",
 		inputs.Strings(),
 		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
@@ -268,7 +331,7 @@
 	`)
 
 	filesystem := result.ModuleForTests("myfilesystem", "android_common_cov")
-	inputs := filesystem.Output("deps.zip").Implicits
+	inputs := filesystem.Output("myfilesystem.img").Implicits
 	android.AssertStringListContains(t, "filesystem should have libfoo(cov)",
 		inputs.Strings(),
 		"out/soong/.intermediates/libfoo/android_arm64_armv8-a_shared_cov/libfoo.so")
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 75abf70..34f4ffb 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -37,7 +37,7 @@
 	module := &systemImage{}
 	module.AddProperties(&module.properties)
 	module.filesystem.buildExtraFiles = module.buildExtraFiles
-	module.filesystem.filterPackagingSpecs = module.filterPackagingSpecs
+	module.filesystem.filterPackagingSpec = module.filterPackagingSpec
 	initFilesystemModule(&module.filesystem)
 	return module
 }
@@ -73,10 +73,6 @@
 // Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
 // Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
-func (s *systemImage) filterPackagingSpecs(specs map[string]android.PackagingSpec) {
-	for k, ps := range specs {
-		if ps.Partition() != "system" {
-			delete(specs, k)
-		}
-	}
+func (s *systemImage) filterPackagingSpec(ps android.PackagingSpec) bool {
+	return ps.Partition() == "system"
 }
diff --git a/genrule/allowlists.go b/genrule/allowlists.go
index 60b1366..7c71b77 100644
--- a/genrule/allowlists.go
+++ b/genrule/allowlists.go
@@ -21,9 +21,4 @@
 		"com.google.pixel.camera.hal.manifest",
 		// go/keep-sorted end
 	}
-
-	SandboxingDenyPathList = []string{
-		// go/keep-sorted start
-		// go/keep-sorted end
-	}
 )
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 6f66088..a2a3f75 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -842,19 +842,15 @@
 
 type sandboxingAllowlistSets struct {
 	sandboxingDenyModuleSet map[string]bool
-	sandboxingDenyPathSet   map[string]bool
 }
 
 func getSandboxingAllowlistSets(ctx android.PathContext) *sandboxingAllowlistSets {
 	return ctx.Config().Once(sandboxingAllowlistKey, func() interface{} {
 		sandboxingDenyModuleSet := map[string]bool{}
-		sandboxingDenyPathSet := map[string]bool{}
 
 		android.AddToStringSet(sandboxingDenyModuleSet, SandboxingDenyModuleList)
-		android.AddToStringSet(sandboxingDenyPathSet, SandboxingDenyPathList)
 		return &sandboxingAllowlistSets{
 			sandboxingDenyModuleSet: sandboxingDenyModuleSet,
-			sandboxingDenyPathSet:   sandboxingDenyPathSet,
 		}
 	}).(*sandboxingAllowlistSets)
 }
@@ -864,8 +860,7 @@
 		return r.SandboxTools()
 	}
 	sandboxingAllowlistSets := getSandboxingAllowlistSets(ctx)
-	if sandboxingAllowlistSets.sandboxingDenyPathSet[ctx.ModuleDir()] ||
-		sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] {
+	if sandboxingAllowlistSets.sandboxingDenyModuleSet[ctx.ModuleName()] {
 		return r.SandboxTools()
 	}
 	return r.SandboxInputs()
diff --git a/java/Android.bp b/java/Android.bp
index 2585cd2..54b36ab 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -67,6 +67,7 @@
         "plugin.go",
         "prebuilt_apis.go",
         "proto.go",
+        "ravenwood.go",
         "robolectric.go",
         "rro.go",
         "sdk.go",
@@ -107,6 +108,7 @@
         "plugin_test.go",
         "prebuilt_apis_test.go",
         "proto_test.go",
+        "ravenwood_test.go",
         "rro_test.go",
         "sdk_library_test.go",
         "sdk_test.go",
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 f61fc83..27dd38b 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -23,6 +23,7 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -203,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,
@@ -414,17 +417,11 @@
 		linkFlags = append(linkFlags, "--static-lib")
 	}
 
+	linkFlags = append(linkFlags, "--no-static-lib-packages")
 	if a.isLibrary && a.useResourceProcessorBusyBox(ctx) {
-		// When building an android_library using ResourceProcessorBusyBox the resources are merged into
-		// package-res.apk with --merge-only, but --no-static-lib-packages is not used so that R.txt only
-		// contains resources from this library.
+		// When building an android_library using ResourceProcessorBusyBox pass --merge-only to skip resource
+		// references validation until the final app link step when all static libraries are present.
 		linkFlags = append(linkFlags, "--merge-only")
-	} else {
-		// When building and app or when building an android_library without ResourceProcessorBusyBox
-		// --no-static-lib-packages is used to put all the resources into the app.  If ResourceProcessorBusyBox
-		// is used then the app's R.txt will be post-processed along with the R.txt files from dependencies to
-		// sort resources into the right packages in R.class.
-		linkFlags = append(linkFlags, "--no-static-lib-packages")
 	}
 
 	packageRes := android.PathForModuleOut(ctx, "package-res.apk")
@@ -458,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())
@@ -465,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.
@@ -559,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
@@ -808,6 +809,9 @@
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	if a.usesLibrary.shouldDisableDexpreopt {
+		a.dexpreopter.disableDexpreopt()
+	}
 	a.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
 			sdkContext:                     android.SdkContext(a),
@@ -1136,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{
@@ -1150,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(),
@@ -1168,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
@@ -1177,6 +1181,7 @@
 		"--static-lib",
 		"--merge-only",
 		"--auto-add-overlay",
+		"--no-static-lib-packages",
 	}
 
 	linkFlags = append(linkFlags, "--manifest "+a.manifest.String())
@@ -1204,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")
@@ -1250,6 +1255,7 @@
 		TransitiveStaticLibsHeaderJars: a.transitiveStaticLibsHeaderJars,
 		ImplementationAndResourcesJars: android.PathsIfNonNil(a.classpathFile),
 		ImplementationJars:             android.PathsIfNonNil(a.classpathFile),
+		StubsLinkType:                  Implementation,
 		// TransitiveAconfigFiles: // TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
 	})
 
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 082b00e..8599003 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -152,9 +152,10 @@
 	if params.SdkContext != nil {
 		targetSdkVersion := targetSdkVersionForManifestFixer(ctx, params)
 
-		if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-			deps = append(deps, ApiFingerprintPath(ctx))
+		if useApiFingerprint, fingerprintTargetSdkVersion, fingerprintDeps :=
+			UseApiFingerprint(ctx); useApiFingerprint && ctx.ModuleName() != "framework-res" {
+			targetSdkVersion = fingerprintTargetSdkVersion
+			deps = append(deps, fingerprintDeps)
 		}
 
 		args = append(args, "--targetSdkVersion ", targetSdkVersion)
@@ -169,9 +170,10 @@
 			ctx.ModuleErrorf("invalid ReplaceMaxSdkVersionPlaceholder: %s", err)
 		}
 
-		if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-			deps = append(deps, ApiFingerprintPath(ctx))
+		if useApiFingerprint, fingerprintMinSdkVersion, fingerprintDeps :=
+			UseApiFingerprint(ctx); useApiFingerprint && ctx.ModuleName() != "framework-res" {
+			minSdkVersion = fingerprintMinSdkVersion
+			deps = append(deps, fingerprintDeps)
 		}
 
 		if err != nil {
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 0c56d81..4f6f1f3 100755
--- a/java/app.go
+++ b/java/app.go
@@ -472,8 +472,9 @@
 	// Add TARGET_AAPT_CHARACTERISTICS values to AAPT link flags if they exist and --product flags were not provided.
 	autogenerateRRO := proptools.Bool(a.appProperties.Generate_product_characteristics_rro)
 	hasProduct := android.PrefixInList(a.aaptProperties.Aaptflags, "--product")
-	if !autogenerateRRO && !hasProduct && len(ctx.Config().ProductAAPTCharacteristics()) > 0 {
-		aaptLinkFlags = append(aaptLinkFlags, "--product", ctx.Config().ProductAAPTCharacteristics())
+	characteristics := ctx.Config().ProductAAPTCharacteristics()
+	if !autogenerateRRO && !hasProduct && len(characteristics) > 0 && characteristics != "default" {
+		aaptLinkFlags = append(aaptLinkFlags, "--product", characteristics)
 	}
 
 	if !Bool(a.aaptProperties.Aapt_include_all_resources) {
@@ -777,6 +778,9 @@
 	a.onDeviceDir = android.InstallPathToOnDevicePath(ctx, a.installDir)
 
 	a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	if a.usesLibrary.shouldDisableDexpreopt {
+		a.dexpreopter.disableDexpreopt()
+	}
 
 	var noticeAssetPath android.WritablePath
 	if Bool(a.appProperties.Embed_notices) || ctx.Config().IsEnvTrue("ALWAYS_EMBED_NOTICES") {
@@ -908,6 +912,13 @@
 	}
 
 	a.buildAppDependencyInfo(ctx)
+
+	providePrebuiltInfo(ctx,
+		prebuiltInfoProps{
+			baseModuleName: a.BaseModuleName(),
+			isPrebuilt:     false,
+		},
+	)
 }
 
 type appDepsInterface interface {
@@ -920,15 +931,39 @@
 	shouldCollectRecursiveNativeDeps bool,
 	checkNativeSdkVersion bool) ([]jniLib, android.Paths, []Certificate) {
 
-	var jniLibs []jniLib
-	var prebuiltJniPackages android.Paths
-	var certificates []Certificate
-	seenModulePaths := make(map[string]bool)
-
 	if checkNativeSdkVersion {
 		checkNativeSdkVersion = app.SdkVersion(ctx).Specified() &&
 			app.SdkVersion(ctx).Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
 	}
+	jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
+		checkNativeSdkVersion, func(dep cc.LinkableInterface) bool {
+			return !dep.IsNdk(ctx.Config()) && !dep.IsStubs()
+		})
+
+	var certificates []Certificate
+
+	ctx.VisitDirectDeps(func(module android.Module) {
+		otherName := ctx.OtherModuleName(module)
+		tag := ctx.OtherModuleDependencyTag(module)
+
+		if tag == certificateTag {
+			if dep, ok := module.(*AndroidAppCertificate); ok {
+				certificates = append(certificates, dep.Certificate)
+			} else {
+				ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName)
+			}
+		}
+	})
+	return jniLib, prebuiltJniPackages, certificates
+}
+
+func collectJniDeps(ctx android.ModuleContext,
+	shouldCollectRecursiveNativeDeps bool,
+	checkNativeSdkVersion bool,
+	filter func(cc.LinkableInterface) bool) ([]jniLib, android.Paths) {
+	var jniLibs []jniLib
+	var prebuiltJniPackages android.Paths
+	seenModulePaths := make(map[string]bool)
 
 	ctx.WalkDeps(func(module android.Module, parent android.Module) bool {
 		otherName := ctx.OtherModuleName(module)
@@ -936,7 +971,7 @@
 
 		if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) {
 			if dep, ok := module.(cc.LinkableInterface); ok {
-				if dep.IsNdk(ctx.Config()) || dep.IsStubs() {
+				if filter != nil && !filter(dep) {
 					return false
 				}
 
@@ -977,18 +1012,10 @@
 			prebuiltJniPackages = append(prebuiltJniPackages, info.JniPackages...)
 		}
 
-		if tag == certificateTag {
-			if dep, ok := module.(*AndroidAppCertificate); ok {
-				certificates = append(certificates, dep.Certificate)
-			} else {
-				ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName)
-			}
-		}
-
 		return false
 	})
 
-	return jniLibs, prebuiltJniPackages, certificates
+	return jniLibs, prebuiltJniPackages
 }
 
 func (a *AndroidApp) WalkPayloadDeps(ctx android.ModuleContext, do android.PayloadDepsCallback) {
@@ -1096,6 +1123,8 @@
 		if a.rJar != nil {
 			return []android.Path{a.rJar}, nil
 		}
+	case ".apk":
+		return []android.Path{a.outputFile}, nil
 	case ".export-package.apk":
 		return []android.Path{a.exportPackage}, nil
 	case ".manifest.xml":
@@ -1190,10 +1219,10 @@
 			Cmd   *string
 		}{
 			Name:  proptools.StringPtr(rroManifestName),
-			Tools: []string{"characteristics_rro_generator"},
+			Tools: []string{"characteristics_rro_generator", "aapt2"},
 			Out:   []string{"AndroidManifest.xml"},
-			Srcs:  []string{":" + a.Name() + "{.manifest.xml}"},
-			Cmd:   proptools.StringPtr("$(location characteristics_rro_generator) $(in) $(out)"),
+			Srcs:  []string{":" + a.Name() + "{.apk}"},
+			Cmd:   proptools.StringPtr("$(location characteristics_rro_generator) $$($(location aapt2) dump packagename $(in)) $(out)"),
 		}
 		ctx.CreateModule(genrule.GenRuleFactory, &rroManifestProperties)
 
@@ -1294,6 +1323,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 {
@@ -1547,6 +1582,9 @@
 
 	// Whether to enforce verify_uses_library check.
 	enforce bool
+
+	// Whether dexpreopt should be disabled
+	shouldDisableDexpreopt bool
 }
 
 func (u *usesLibrary) addLib(lib string, optional bool) {
@@ -1628,6 +1666,15 @@
 			}
 		}
 
+		// Skip java_sdk_library dependencies that provide stubs, but not an implementation.
+		// This will be restricted to optional_uses_libs
+		if sdklib, ok := m.(SdkLibraryDependency); ok {
+			if tag == usesLibOptTag && sdklib.DexJarBuildPath(ctx).PathOrNil() == nil {
+				u.shouldDisableDexpreopt = true
+				return
+			}
+		}
+
 		if lib, ok := m.(UsesLibraryDependency); ok {
 			libName := dep
 			if ulib, ok := m.(ProvidesUsesLib); ok && ulib.ProvidesUsesLib() != nil {
diff --git a/java/app_import.go b/java/app_import.go
index 12ead0a..7387e16 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -70,9 +70,10 @@
 	android.ApexModuleBase
 	prebuilt android.Prebuilt
 
-	properties   AndroidAppImportProperties
-	dpiVariants  interface{}
-	archVariants interface{}
+	properties       AndroidAppImportProperties
+	dpiVariants      interface{}
+	archVariants     interface{}
+	arch_dpiVariants interface{}
 
 	outputFile  android.Path
 	certificate Certificate
@@ -144,6 +145,16 @@
 	// 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
+
+	// Path to the .prebuilt_info file of the prebuilt app.
+	// In case of mainline modules, the .prebuilt_info file contains the build_id that was used
+	// to generate the prebuilt.
+	Prebuilt_info *string `android:"path"`
 }
 
 func (a *AndroidAppImport) IsInstallable() bool {
@@ -155,8 +166,8 @@
 // soong config variables are applied.
 func (a *AndroidAppImport) processVariants(ctx android.DefaultableHookContext) {
 	config := ctx.Config()
+	dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName(DpiGroupName)
 
-	dpiProps := reflect.ValueOf(a.dpiVariants).Elem().FieldByName("Dpi_variants")
 	// Try DPI variant matches in the reverse-priority order so that the highest priority match
 	// overwrites everything else.
 	// TODO(jungjw): Can we optimize this by making it priority order?
@@ -166,11 +177,27 @@
 	if config.ProductAAPTPreferredConfig() != "" {
 		MergePropertiesFromVariant(ctx, &a.properties, dpiProps, config.ProductAAPTPreferredConfig())
 	}
-
-	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName("Arch")
+	archProps := reflect.ValueOf(a.archVariants).Elem().FieldByName(ArchGroupName)
 	archType := ctx.Config().AndroidFirstDeviceTarget.Arch.ArchType
 	MergePropertiesFromVariant(ctx, &a.properties, archProps, archType.Name)
 
+	// Process "arch" includes "dpi_variants"
+	archStructPtr := reflect.ValueOf(a.arch_dpiVariants).Elem().FieldByName(ArchGroupName)
+	if archStruct := archStructPtr.Elem(); archStruct.IsValid() {
+		archPartPropsPtr := archStruct.FieldByName(proptools.FieldNameForProperty(archType.Name))
+		if archPartProps := archPartPropsPtr.Elem(); archPartProps.IsValid() {
+			archDpiPropsPtr := archPartProps.FieldByName(DpiGroupName)
+			if archDpiProps := archDpiPropsPtr.Elem(); archDpiProps.IsValid() {
+				for i := len(config.ProductAAPTPrebuiltDPI()) - 1; i >= 0; i-- {
+					MergePropertiesFromVariant(ctx, &a.properties, archDpiProps, config.ProductAAPTPrebuiltDPI()[i])
+				}
+				if config.ProductAAPTPreferredConfig() != "" {
+					MergePropertiesFromVariant(ctx, &a.properties, archDpiProps, config.ProductAAPTPreferredConfig())
+				}
+			}
+		}
+	}
+
 	if String(a.properties.Apk) == "" {
 		// Disable this module since the apk property is still empty after processing all matching
 		// variants. This likely means there is no matching variant, and the default variant doesn't
@@ -257,6 +284,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.")
@@ -319,6 +350,9 @@
 
 	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
 	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+	if a.usesLibrary.shouldDisableDexpreopt {
+		a.dexpreopter.disableDexpreopt()
+	}
 
 	if a.usesLibrary.enforceUsesLibraries() {
 		a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk)
@@ -384,6 +418,14 @@
 	}
 	android.CollectDependencyAconfigFiles(ctx, &a.mergedAconfigFiles)
 
+	providePrebuiltInfo(ctx,
+		prebuiltInfoProps{
+			baseModuleName: a.BaseModuleName(),
+			isPrebuilt:     true,
+			prebuiltInfo:   a.properties.Prebuilt_info,
+		},
+	)
+
 	// TODO: androidmk converter jni libs
 }
 
@@ -448,18 +490,25 @@
 	return android.OptionalPath{}
 }
 
+const (
+	ArchGroupName = "Arch"
+	DpiGroupName  = "Dpi_variants"
+)
+
 var dpiVariantGroupType reflect.Type
 var archVariantGroupType reflect.Type
+var archdpiVariantGroupType reflect.Type
 var supportedDpis = []string{"ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"}
 
 func initAndroidAppImportVariantGroupTypes() {
-	dpiVariantGroupType = createVariantGroupType(supportedDpis, "Dpi_variants")
+	dpiVariantGroupType = createVariantGroupType(supportedDpis, DpiGroupName)
 
 	archNames := make([]string, len(android.ArchTypeList()))
 	for i, archType := range android.ArchTypeList() {
 		archNames[i] = archType.Name
 	}
-	archVariantGroupType = createVariantGroupType(archNames, "Arch")
+	archVariantGroupType = createVariantGroupType(archNames, ArchGroupName)
+	archdpiVariantGroupType = createArchDpiVariantGroupType(archNames, supportedDpis)
 }
 
 // Populates all variant struct properties at creation time.
@@ -469,6 +518,9 @@
 
 	a.archVariants = reflect.New(archVariantGroupType).Interface()
 	a.AddProperties(a.archVariants)
+
+	a.arch_dpiVariants = reflect.New(archdpiVariantGroupType).Interface()
+	a.AddProperties(a.arch_dpiVariants)
 }
 
 func (a *AndroidAppImport) Privileged() bool {
@@ -523,6 +575,42 @@
 	})
 }
 
+func createArchDpiVariantGroupType(archNames []string, dpiNames []string) reflect.Type {
+	props := reflect.TypeOf((*AndroidAppImportProperties)(nil))
+
+	dpiVariantFields := make([]reflect.StructField, len(dpiNames))
+	for i, variant_dpi := range dpiNames {
+		dpiVariantFields[i] = reflect.StructField{
+			Name: proptools.FieldNameForProperty(variant_dpi),
+			Type: props,
+		}
+	}
+	dpiVariantGroupStruct := reflect.StructOf(dpiVariantFields)
+	dpi_struct := reflect.StructOf([]reflect.StructField{
+		{
+			Name: DpiGroupName,
+			Type: reflect.PointerTo(dpiVariantGroupStruct),
+		},
+	})
+
+	archVariantFields := make([]reflect.StructField, len(archNames))
+	for i, variant_arch := range archNames {
+		archVariantFields[i] = reflect.StructField{
+			Name: proptools.FieldNameForProperty(variant_arch),
+			Type: reflect.PointerTo(dpi_struct),
+		}
+	}
+	archVariantGroupStruct := reflect.StructOf(archVariantFields)
+
+	return_struct := reflect.StructOf([]reflect.StructField{
+		{
+			Name: ArchGroupName,
+			Type: reflect.PointerTo(archVariantGroupStruct),
+		},
+	})
+	return return_struct
+}
+
 // android_app_import imports a prebuilt apk with additional processing specified in the module.
 // DPI-specific apk source files can be specified using dpi_variants. Example:
 //
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 44f8f16..5de50e7 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -472,6 +472,35 @@
 			artifactPath: "prebuilts/apk/app_arm.apk",
 			installPath:  "/system/app/foo/foo.apk",
 		},
+		{
+			name: "matching arch and dpi_variants",
+			bp: `
+				android_app_import {
+					name: "foo",
+					apk: "prebuilts/apk/app.apk",
+					arch: {
+						arm64: {
+							apk: "prebuilts/apk/app_arm64.apk",
+							dpi_variants: {
+								mdpi: {
+									apk: "prebuilts/apk/app_arm64_mdpi.apk",
+								},
+								xhdpi: {
+									apk: "prebuilts/apk/app_arm64_xhdpi.apk",
+								},
+							},
+						},
+					},
+					presigned: true,
+					dex_preopt: {
+						enabled: true,
+					},
+				}
+			`,
+			expected:     "verify_uses_libraries/apk/app_arm64_xhdpi.apk",
+			artifactPath: "prebuilts/apk/app_arm64_xhdpi.apk",
+			installPath:  "/system/app/foo/foo.apk",
+		},
 	}
 
 	for _, test := range testCases {
diff --git a/java/app_set.go b/java/app_set.go
index d2d3b06..33d3ade 100644
--- a/java/app_set.go
+++ b/java/app_set.go
@@ -48,6 +48,11 @@
 	// Names of modules to be overridden. Listed modules can only be other apps
 	//	(in Make or Soong).
 	Overrides []string
+
+	// Path to the .prebuilt_info file of the prebuilt app.
+	// In case of mainline modules, the .prebuilt_info file contains the build_id that was used
+	// to generate the prebuilt.
+	Prebuilt_info *string `android:"path"`
 }
 
 type AndroidAppSet struct {
@@ -117,6 +122,27 @@
 	return result
 }
 
+type prebuiltInfoProps struct {
+	baseModuleName string
+	isPrebuilt     bool
+	prebuiltInfo   *string
+}
+
+// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
+// with information about whether source or prebuilt of an apex was used during the build.
+func providePrebuiltInfo(ctx android.ModuleContext, p prebuiltInfoProps) {
+	info := android.PrebuiltInfo{
+		Name:        p.baseModuleName,
+		Is_prebuilt: p.isPrebuilt,
+	}
+	// If Prebuilt_info information is available in the soong module definition, add it to prebuilt_info.json.
+	if p.prebuiltInfo != nil {
+		prebuiltInfoFile := android.PathForModuleSrc(ctx, *p.prebuiltInfo)
+		info.Prebuilt_info_file_path = prebuiltInfoFile.String()
+	}
+	android.SetProvider(ctx, android.PrebuiltInfoProvider, info)
+}
+
 func (as *AndroidAppSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	as.packedOutput = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
 	as.primaryOutput = android.PathForModuleOut(ctx, as.BaseModuleName()+".apk")
@@ -157,6 +183,15 @@
 		installDir = android.PathForModuleInstall(ctx, "app", as.BaseModuleName())
 	}
 	ctx.InstallFileWithExtraFilesZip(installDir, as.BaseModuleName()+".apk", as.primaryOutput, as.packedOutput)
+
+	providePrebuiltInfo(ctx,
+		prebuiltInfoProps{
+			baseModuleName: as.BaseModuleName(),
+			isPrebuilt:     true,
+			prebuiltInfo:   as.properties.Prebuilt_info,
+		},
+	)
+
 }
 
 func (as *AndroidAppSet) InstallBypassMake() bool { return true }
diff --git a/java/app_test.go b/java/app_test.go
index 362bef9..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",
 			},
@@ -4378,3 +4378,26 @@
 		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
 	)
 }
+
+// Test that dexpreopt is disabled if an optional_uses_libs exists, but does not provide an implementation.
+func TestNoDexpreoptOptionalUsesLibDoesNotHaveImpl(t *testing.T) {
+	bp := `
+		java_sdk_library_import {
+			name: "sdklib_noimpl",
+			public: {
+				jars: ["stub.jar"],
+			},
+		}
+		android_app {
+			name: "app",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			optional_uses_libs: [
+				"sdklib_noimpl",
+			],
+		}
+	`
+	result := prepareForJavaTest.RunTestWithBp(t, bp)
+	dexpreopt := result.ModuleForTests("app", "android_common").MaybeRule("dexpreopt").Rule
+	android.AssertBoolEquals(t, "dexpreopt should be disabled if optional_uses_libs does not have an implementation", true, dexpreopt == nil)
+}
diff --git a/java/base.go b/java/base.go
index 284ec99..69f88be 100644
--- a/java/base.go
+++ b/java/base.go
@@ -94,6 +94,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
 
@@ -205,6 +208,13 @@
 	// Note that currently not all actions implemented by android_apps are sandboxed, so you
 	// may only see this being necessary in lint builds.
 	Compile_data []string `android:"path"`
+
+	// Property signifying whether the module compiles stubs or not.
+	// Should be set to true when srcs of this module are stub files.
+	// This property does not need to be set to true when the module depends on
+	// the stubs via libs, but should be set to true when the module depends on
+	// the stubs via static libs.
+	Is_stubs_module *bool
 }
 
 // Properties that are specific to device modules. Host module factories should not add these when
@@ -532,6 +542,8 @@
 	// Values that will be set in the JarJarProvider data for jarjar repackaging,
 	// and merged with our dependencies' rules.
 	jarjarRenameRules map[string]string
+
+	stubsLinkType StubsLinkType
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -1091,11 +1103,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
+			}
 		}
 	}
 
@@ -1212,6 +1226,7 @@
 			ExportedPlugins:                j.exportedPluginJars,
 			ExportedPluginClasses:          j.exportedPluginClasses,
 			ExportedPluginDisableTurbine:   j.exportedDisableTurbine,
+			StubsLinkType:                  j.stubsLinkType,
 		})
 
 		j.outputFile = j.headerJarFile
@@ -1296,7 +1311,7 @@
 		}
 	}
 
-	jars := append(android.Paths(nil), kotlinJars...)
+	jars := slices.Clone(kotlinJars)
 
 	j.compiledSrcJars = srcJars
 
@@ -1311,7 +1326,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() {
@@ -1385,6 +1400,8 @@
 		}
 	}
 
+	jars = append(jars, extraCombinedJars...)
+
 	j.srcJarArgs, j.srcJarDeps = resourcePathsToJarArgs(srcFiles), srcFiles
 
 	var includeSrcJar android.WritablePath
@@ -1471,8 +1488,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
@@ -1729,6 +1744,7 @@
 		ExportedPluginClasses:          j.exportedPluginClasses,
 		ExportedPluginDisableTurbine:   j.exportedDisableTurbine,
 		JacocoReportClassesFile:        j.jacocoReportClassesFile,
+		StubsLinkType:                  j.stubsLinkType,
 	})
 
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
@@ -2372,16 +2388,32 @@
 // classes until a module with jarjar_prefix is reached, and all as yet unrenamed classes will then
 // be renamed from that module.
 // TODO: Add another property to suppress the forwarding of
+type DependencyUse int
+
+const (
+	RenameUseInvalid DependencyUse = iota
+	RenameUseInclude
+	RenameUseExclude
+)
+
+type RenameUseElement struct {
+	DepName   string
+	RenameUse DependencyUse
+	Why       string // token for determining where in the logic the decision was made.
+}
+
 type JarJarProviderData struct {
 	// Mapping of class names: original --> renamed.  If the value is "", the class will be
 	// renamed by the next rdep that has the jarjar_prefix attribute (or this module if it has
 	// attribute). Rdeps of that module will inherit the renaming.
-	Rename map[string]string
+	Rename    map[string]string
+	RenameUse []RenameUseElement
 }
 
 func (this JarJarProviderData) GetDebugString() string {
 	result := ""
-	for k, v := range this.Rename {
+	for _, k := range android.SortedKeys(this.Rename) {
+		v := this.Rename[k]
 		if strings.Contains(k, "android.companion.virtual.flags.FakeFeatureFlagsImpl") {
 			result += k + "--&gt;" + v + ";"
 		}
@@ -2440,14 +2472,114 @@
 func collectDirectDepsProviders(ctx android.ModuleContext) (result *JarJarProviderData) {
 	// Gather repackage information from deps
 	// If the dep jas a JarJarProvider, it is used.  Otherwise, any BaseJarJarProvider is used.
+
+	module := ctx.Module()
+	moduleName := module.Name()
+
 	ctx.VisitDirectDepsIgnoreBlueprint(func(m android.Module) {
-		merge := func(theirs *JarJarProviderData) {
-			for orig, renamed := range theirs.Rename {
-				if result == nil {
-					result = &JarJarProviderData{
-						Rename: make(map[string]string),
+		tag := ctx.OtherModuleDependencyTag(m)
+		// This logic mirrors that in (*Module).collectDeps above.  There are several places
+		// where we explicitly return RenameUseExclude, even though it is the default, to
+		// indicate that it has been verified to be the case.
+		//
+		// Note well: there are probably cases that are getting to the unconditional return
+		// and are therefore wrong.
+		shouldIncludeRenames := func() (DependencyUse, string) {
+			if moduleName == m.Name() {
+				return RenameUseInclude, "name" // If we have the same module name, include the renames.
+			}
+			if sc, ok := module.(android.SdkContext); ok {
+				if ctx.Device() {
+					sdkDep := decodeSdkDep(ctx, sc)
+					if !sdkDep.invalidVersion && sdkDep.useFiles {
+						return RenameUseExclude, "useFiles"
 					}
 				}
+			}
+			if IsJniDepTag(tag) || tag == certificateTag || tag == proguardRaiseTag {
+				return RenameUseExclude, "tags"
+			}
+			if _, ok := m.(SdkLibraryDependency); ok {
+				switch tag {
+				case sdkLibTag, libTag:
+					return RenameUseExclude, "sdklibdep" // matches collectDeps()
+				}
+				return RenameUseInvalid, "sdklibdep" // dep is not used in collectDeps()
+			} else if ji, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+				switch ji.StubsLinkType {
+				case Stubs:
+					return RenameUseExclude, "info"
+				case Implementation:
+					return RenameUseInclude, "info"
+				default:
+					//fmt.Printf("LJ: %v -> %v StubsLinkType unknown\n", module, m)
+					// Fall through to the heuristic logic.
+				}
+				switch reflect.TypeOf(m).String() {
+				case "*java.GeneratedJavaLibraryModule":
+					// Probably a java_aconfig_library module.
+					// TODO: make this check better.
+					return RenameUseInclude, "reflect"
+				}
+				switch tag {
+				case bootClasspathTag:
+					return RenameUseExclude, "tagswitch"
+				case sdkLibTag, libTag, instrumentationForTag:
+					return RenameUseInclude, "tagswitch"
+				case java9LibTag:
+					return RenameUseExclude, "tagswitch"
+				case staticLibTag:
+					return RenameUseInclude, "tagswitch"
+				case pluginTag:
+					return RenameUseInclude, "tagswitch"
+				case errorpronePluginTag:
+					return RenameUseInclude, "tagswitch"
+				case exportedPluginTag:
+					return RenameUseInclude, "tagswitch"
+				case kotlinStdlibTag, kotlinAnnotationsTag:
+					return RenameUseExclude, "tagswitch"
+				case kotlinPluginTag:
+					return RenameUseInclude, "tagswitch"
+				default:
+					return RenameUseExclude, "tagswitch"
+				}
+			} else if _, ok := m.(android.SourceFileProducer); ok {
+				switch tag {
+				case sdkLibTag, libTag, staticLibTag:
+					return RenameUseInclude, "srcfile"
+				default:
+					return RenameUseExclude, "srcfile"
+				}
+			} else if _, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
+				return RenameUseInclude, "aconfig_declarations_group"
+			} else {
+				switch tag {
+				case bootClasspathTag:
+					return RenameUseExclude, "else"
+				case systemModulesTag:
+					return RenameUseInclude, "else"
+				}
+			}
+			// If we got here, choose the safer option, which may lead to a build failure, rather
+			// than runtime failures on the device.
+			return RenameUseExclude, "end"
+		}
+
+		if result == nil {
+			result = &JarJarProviderData{
+				Rename:    make(map[string]string),
+				RenameUse: make([]RenameUseElement, 0),
+			}
+		}
+		how, why := shouldIncludeRenames()
+		result.RenameUse = append(result.RenameUse, RenameUseElement{DepName: m.Name(), RenameUse: how, Why: why})
+		if how != RenameUseInclude {
+			// Nothing to merge.
+			return
+		}
+
+		merge := func(theirs *JarJarProviderData) {
+			for orig, renamed := range theirs.Rename {
 				if preexisting, exists := (*result).Rename[orig]; !exists || preexisting == "" {
 					result.Rename[orig] = renamed
 				} else if preexisting != "" && renamed != "" && preexisting != renamed {
@@ -2537,7 +2669,8 @@
 // to "" won't be in this list because they shouldn't be renamed yet.
 func getJarJarRuleText(provider *JarJarProviderData) string {
 	result := ""
-	for orig, renamed := range provider.Rename {
+	for _, orig := range android.SortedKeys(provider.Rename) {
+		renamed := provider.Rename[orig]
 		if renamed != "" {
 			result += "rule " + orig + " " + renamed + "\n"
 		}
diff --git a/java/builder.go b/java/builder.go
index 085e7a1..5d84d0b 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -120,6 +120,8 @@
 				`--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED ` +
 				`--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED ` +
 				`--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED ` +
+				`--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED ` +
+				`--add-exports=jdk.internal.opt/jdk.internal.opt=ALL-UNNAMED ` +
 				`-jar ${config.JavaKytheExtractorJar} ` +
 				`${config.JavacHeapFlags} ${config.CommonJdkFlags} ` +
 				`$processorpath $processor $javacFlags $bootClasspath $classpath ` +
@@ -212,6 +214,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{
@@ -277,7 +287,7 @@
 
 	gatherReleasedFlaggedApisRule = pctx.AndroidStaticRule("gatherReleasedFlaggedApisRule",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --format='{fully_qualified_name}={state:bool}' ` +
+			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}' ` +
 				`--out ${out} ` +
 				`${flags_path} ` +
 				`${filter_args} `,
@@ -418,7 +428,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 +453,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 +499,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 +552,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 +571,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 +614,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 +673,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/config.go b/java/config/config.go
index 6a945ac..d720046 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -131,12 +131,7 @@
 		if override := ctx.Config().Getenv("OVERRIDE_JLINK_VERSION_NUMBER"); override != "" {
 			return override
 		}
-		switch ctx.Config().Getenv("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
-		case "true":
-			return "21"
-		default:
-			return "17"
-		}
+		return "21"
 	})
 
 	pctx.SourcePathVariable("JavaToolchain", "${JavaHome}/bin")
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/core-libraries/Android.bp b/java/core-libraries/Android.bp
index 8ffe511..ab72e8b 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -64,6 +64,7 @@
         "stub-annotations",
     ],
     enable_validation: false,
+    stubs_type: "everything",
 }
 
 java_library {
@@ -248,6 +249,7 @@
         "stub-annotations",
     ],
     visibility: ["//visibility:private"],
+    stubs_type: "everything",
 }
 
 // Produces a dist file that is used by the
@@ -358,6 +360,7 @@
     libs: [
         "stub-annotations",
     ],
+    stubs_type: "everything",
 }
 
 java_library {
@@ -446,6 +449,7 @@
     libs: [
         "stub-annotations",
     ],
+    stubs_type: "everything",
 }
 
 java_library {
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index efd13b8..3f8735c 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -137,6 +137,7 @@
 		ResourceJars:                   d.resourceJars,
 		SrcJarArgs:                     d.srcJarArgs,
 		SrcJarDeps:                     d.srcJarDeps,
+		StubsLinkType:                  Implementation,
 		// TODO: Not sure if aconfig flags that have been moved between device and host variants
 		// make sense.
 	})
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 9db9b1b..38ed856 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -102,6 +102,11 @@
 	dexpreoptProperties       DexpreoptProperties
 	importDexpreoptProperties ImportDexpreoptProperties
 
+	// If true, the dexpreopt rules will not be generated
+	// Unlike Dex_preopt.Enabled which is user-facing,
+	// shouldDisableDexpreopt is a mutated propery.
+	shouldDisableDexpreopt bool
+
 	installPath         android.InstallPath
 	uncompressedDex     bool
 	isSDKLibrary        bool
@@ -182,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 {
@@ -197,6 +229,10 @@
 		return true
 	}
 
+	if d.shouldDisableDexpreopt {
+		return true
+	}
+
 	// If the module is from a prebuilt APEX, it shouldn't be installable, but it can still be
 	// dexpreopted.
 	if !ctx.Module().(DexpreopterInterface).IsInstallable() && !forPrebuiltApex(ctx) {
@@ -207,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
@@ -528,3 +568,7 @@
 func (d *dexpreopter) OutputProfilePathOnHost() android.Path {
 	return d.outputProfilePathOnHost
 }
+
+func (d *dexpreopter) disableDexpreopt() {
+	d.shouldDisableDexpreopt = true
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index cfbf2b4..aec40b3 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -413,9 +413,12 @@
 		case aconfigDeclarationTag:
 			if dep, ok := android.OtherModuleProvider(ctx, module, android.AconfigDeclarationsProviderKey); ok {
 				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPath)
+			} else if dep, ok := android.OtherModuleProvider(ctx, module, android.CodegenInfoProvider); ok {
+				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
 			} else {
-				ctx.ModuleErrorf("Only aconfig_declarations module type is allowed for "+
-					"flags_packages property, but %s is not aconfig_declarations module type",
+				ctx.ModuleErrorf("Only aconfig_declarations and aconfig_declarations_group "+
+					"module type is allowed for flags_packages property, but %s is neither "+
+					"of these supported module types",
 					module.Name(),
 				)
 			}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 56ae427..9556e95 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -52,6 +52,19 @@
 	}
 }
 
+func StringToStubsType(s string) StubsType {
+	switch strings.ToLower(s) {
+	case Everything.String():
+		return Everything
+	case Runtime.String():
+		return Runtime
+	case Exportable.String():
+		return Exportable
+	default:
+		return Unavailable
+	}
+}
+
 func init() {
 	RegisterStubsBuildComponents(android.InitRegistrationContext)
 }
@@ -355,7 +368,7 @@
 		ret, err = nil, fmt.Errorf("api file path not supported for the stub type %s", stubsType.String())
 	}
 	if ret == nil && err == nil {
-		err = fmt.Errorf("stubs srcjar is null for the stub type %s", stubsType.String())
+		err = fmt.Errorf("api file is null for the stub type %s", stubsType.String())
 	}
 	return ret, err
 }
@@ -465,34 +478,41 @@
 }
 
 func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath, stubsType StubsType, checkApi bool) {
-	if checkApi || String(d.properties.Api_filename) != "" {
-		filename := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt")
-		uncheckedApiFile := android.PathForModuleOut(ctx, stubsType.String(), filename)
-		cmd.FlagWithOutput("--api ", uncheckedApiFile)
 
+	apiFileName := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt")
+	uncheckedApiFile := android.PathForModuleOut(ctx, stubsType.String(), apiFileName)
+	cmd.FlagWithOutput("--api ", uncheckedApiFile)
+	if checkApi || String(d.properties.Api_filename) != "" {
 		if stubsType == Everything {
 			d.apiFile = uncheckedApiFile
 		} else if stubsType == Exportable {
 			d.exportableApiFile = uncheckedApiFile
 		}
 	} else if sourceApiFile := proptools.String(d.properties.Check_api.Current.Api_file); sourceApiFile != "" {
-		// If check api is disabled then make the source file available for export.
-		d.apiFile = android.PathForModuleSrc(ctx, sourceApiFile)
+		if stubsType == Everything {
+			// If check api is disabled then make the source file available for export.
+			d.apiFile = android.PathForModuleSrc(ctx, sourceApiFile)
+		} else if stubsType == Exportable {
+			d.exportableApiFile = uncheckedApiFile
+		}
 	}
 
+	removedApiFileName := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt")
+	uncheckedRemovedFile := android.PathForModuleOut(ctx, stubsType.String(), removedApiFileName)
+	cmd.FlagWithOutput("--removed-api ", uncheckedRemovedFile)
 	if checkApi || String(d.properties.Removed_api_filename) != "" {
-		filename := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt")
-		uncheckedRemovedFile := android.PathForModuleOut(ctx, stubsType.String(), filename)
-		cmd.FlagWithOutput("--removed-api ", uncheckedRemovedFile)
-
 		if stubsType == Everything {
 			d.removedApiFile = uncheckedRemovedFile
 		} else if stubsType == Exportable {
 			d.exportableRemovedApiFile = uncheckedRemovedFile
 		}
 	} else if sourceRemovedApiFile := proptools.String(d.properties.Check_api.Current.Removed_api_file); sourceRemovedApiFile != "" {
-		// If check api is disabled then make the source removed api file available for export.
-		d.removedApiFile = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
+		if stubsType == Everything {
+			// If check api is disabled then make the source removed api file available for export.
+			d.removedApiFile = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
+		} else if stubsType == Exportable {
+			d.exportableRemovedApiFile = uncheckedRemovedFile
+		}
 	}
 
 	if stubsDir.Valid() {
@@ -723,7 +743,7 @@
 // defined for a module, simply revert all flagged apis annotations. If aconfig_declarations
 // property is defined, apply transformations and only revert the flagged apis that are not
 // enabled via release configurations and are not specified in aconfig_declarations
-func (d *Droidstubs) generateRevertAnnotationArgs(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsType StubsType, aconfigFlagsPaths android.Paths) {
+func generateRevertAnnotationArgs(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsType StubsType, aconfigFlagsPaths android.Paths) {
 
 	if len(aconfigFlagsPaths) == 0 {
 		cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
@@ -744,8 +764,14 @@
 		filterArgs = "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
 
 	case Exportable:
-		filterArgs = "--filter='state:ENABLED+permission:READ_ONLY'"
-
+		// When the build flag RELEASE_EXPORT_RUNTIME_APIS is set to true, apis marked with
+		// the flagged apis that have read_write permissions are exposed on top of the enabled
+		// and read_only apis. This is to support local override of flag values at runtime.
+		if ctx.Config().ReleaseExportRuntimeApis() {
+			filterArgs = "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
+		} else {
+			filterArgs = "--filter='state:ENABLED+permission:READ_ONLY'"
+		}
 	}
 
 	ctx.Build(pctx, android.BuildParams{
@@ -1057,7 +1083,7 @@
 
 	cmd := d.commonMetalavaStubCmd(ctx, rule, params)
 
-	d.generateRevertAnnotationArgs(ctx, cmd, params.stubConfig.stubsType, params.stubConfig.deps.aconfigProtoFiles)
+	generateRevertAnnotationArgs(ctx, cmd, params.stubConfig.stubsType, params.stubConfig.deps.aconfigProtoFiles)
 
 	if params.stubConfig.doApiLint {
 		// Pass the lint baseline file as an input to resolve the lint errors.
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 52cd1c5..8da695f 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -22,6 +22,8 @@
 	"testing"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func TestDroidstubs(t *testing.T) {
@@ -335,6 +337,7 @@
 			name: "bar",
 			api_surface: "public",
 			api_contributions: ["foo.api.contribution"],
+			stubs_type: "everything",
 		}
 	`
 	ctx, _ := testJavaWithFS(t, `
@@ -412,3 +415,48 @@
 	android.AssertStringDoesContain(t, "foo generates exportable stubs jar",
 		strings.Join(m.AllOutputs(), ""), "exportable/foo-stubs.srcjar")
 }
+
+func TestReleaseExportRuntimeApis(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.BuildFlags = map[string]string{
+				"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
+			}
+			variables.ExportRuntimeApis = proptools.BoolPtr(true)
+		}),
+		android.FixtureMergeMockFs(map[string][]byte{
+			"a/A.java":      nil,
+			"a/current.txt": nil,
+			"a/removed.txt": nil,
+		}),
+	).RunTestWithBp(t, `
+	aconfig_declarations {
+		name: "bar",
+		package: "com.example.package",
+		srcs: [
+			"bar.aconfig",
+		],
+	}
+	droidstubs {
+		name: "foo",
+		srcs: ["a/A.java"],
+		api_surface: "public",
+		check_api: {
+			current: {
+				api_file: "a/current.txt",
+				removed_api_file: "a/removed.txt",
+			}
+		},
+		aconfig_declarations: [
+			"bar",
+		],
+	}
+	`)
+
+	m := result.ModuleForTests("foo", "android_common")
+
+	rule := m.Output("released-flagged-apis-exportable.txt")
+	exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
+	android.AssertStringEquals(t, "Filter argument expected to contain READ_WRITE permissions", exposeWritableApisFilter, rule.Args["filter_args"])
+}
diff --git a/java/hiddenapi_monolithic.go b/java/hiddenapi_monolithic.go
index a61018d..1e30c5f 100644
--- a/java/hiddenapi_monolithic.go
+++ b/java/hiddenapi_monolithic.go
@@ -68,7 +68,7 @@
 		case *ClasspathFragmentElement:
 			fragment := e.Module()
 			if info, ok := android.OtherModuleProvider(ctx, fragment, HiddenAPIInfoProvider); ok {
-				monolithicInfo.append(&info)
+				monolithicInfo.append(ctx, fragment, &info)
 			} else {
 				ctx.ModuleErrorf("%s does not provide hidden API information", fragment)
 			}
@@ -79,14 +79,25 @@
 }
 
 // append appends all the files from the supplied info to the corresponding files in this struct.
-func (i *MonolithicHiddenAPIInfo) append(other *HiddenAPIInfo) {
+func (i *MonolithicHiddenAPIInfo) append(ctx android.ModuleContext, otherModule android.Module, other *HiddenAPIInfo) {
 	i.FlagsFilesByCategory.append(other.FlagFilesByCategory)
 	i.AnnotationFlagsPaths = append(i.AnnotationFlagsPaths, other.AnnotationFlagsPath)
 	i.MetadataPaths = append(i.MetadataPaths, other.MetadataPath)
 	i.IndexPaths = append(i.IndexPaths, other.IndexPath)
 
-	i.StubFlagSubsets = append(i.StubFlagSubsets, other.StubFlagSubset())
-	i.FlagSubsets = append(i.FlagSubsets, other.FlagSubset())
+	apexInfo, ok := android.OtherModuleProvider(ctx, otherModule, android.ApexInfoProvider)
+	if !ok {
+		ctx.ModuleErrorf("Could not determine min_version_version of %s\n", otherModule.Name())
+		return
+	}
+	if apexInfo.MinSdkVersion.LessThanOrEqualTo(android.ApiLevelR) {
+		// Restrict verify_overlaps to R and older modules.
+		// The runtime in S does not have the same restriction that
+		// requires the hiddenapi flags to be generated in a monolithic
+		// invocation.
+		i.StubFlagSubsets = append(i.StubFlagSubsets, other.StubFlagSubset())
+		i.FlagSubsets = append(i.FlagSubsets, other.FlagSubset())
+	}
 }
 
 var MonolithicHiddenAPIInfoProvider = blueprint.NewProvider[MonolithicHiddenAPIInfo]()
diff --git a/java/java.go b/java/java.go
index d7d271c..e606993 100644
--- a/java/java.go
+++ b/java/java.go
@@ -87,6 +87,14 @@
 	android.RegisterSdkMemberType(javaTestSdkMemberType)
 }
 
+type StubsLinkType int
+
+const (
+	Unknown StubsLinkType = iota
+	Stubs
+	Implementation
+)
+
 var (
 	// Supports adding java header libraries to module_exports and sdk.
 	javaHeaderLibsSdkMemberType = &librarySdkMemberType{
@@ -296,6 +304,11 @@
 	// JacocoReportClassesFile is the path to a jar containing uninstrumented classes that will be
 	// instrumented by jacoco.
 	JacocoReportClassesFile android.Path
+
+	// StubsLinkType provides information about whether the provided jars are stub jars or
+	// implementation jars. If the provider is set by java_sdk_library, the link type is "unknown"
+	// and selection between the stub jar vs implementation jar is deferred to SdkLibrary.sdkJars(...)
+	StubsLinkType StubsLinkType
 }
 
 var JavaInfoProvider = blueprint.NewProvider[JavaInfo]()
@@ -332,6 +345,12 @@
 	return j.kytheFiles
 }
 
+func (d dependencyTag) PropagateAconfigValidation() bool {
+	return d.static
+}
+
+var _ android.PropagateAconfigValidationDependencyTag = dependencyTag{}
+
 type dependencyTag struct {
 	blueprint.BaseDependencyTag
 	name string
@@ -341,6 +360,8 @@
 
 	// True if the dependency is a toolchain, for example an annotation processor.
 	toolchain bool
+
+	static bool
 }
 
 // installDependencyTag is a dependency tag that is annotated to cause the installed files of the
@@ -386,7 +407,7 @@
 var (
 	dataNativeBinsTag       = dependencyTag{name: "dataNativeBins"}
 	dataDeviceBinsTag       = dependencyTag{name: "dataDeviceBins"}
-	staticLibTag            = dependencyTag{name: "staticlib"}
+	staticLibTag            = dependencyTag{name: "staticlib", static: true}
 	libTag                  = dependencyTag{name: "javalib", runtimeLinked: true}
 	sdkLibTag               = dependencyTag{name: "sdklib", runtimeLinked: true}
 	java9LibTag             = dependencyTag{name: "java9lib", runtimeLinked: true}
@@ -666,10 +687,11 @@
 		return true
 	}
 
-	// Store uncompressed dex files that are preopted on /system.
-	if !dexpreopter.dexpreoptDisabled(ctx, libName) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, libName, dexpreopter.installPath)) {
+	// Store uncompressed dex files that are preopted on /system or /system_other.
+	if !dexpreopter.dexpreoptDisabled(ctx, libName) {
 		return true
 	}
+
 	if ctx.Config().UncompressPrivAppDex() &&
 		inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules()) {
 		return true
@@ -686,14 +708,195 @@
 	}
 }
 
-func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+// list of java_library modules that set platform_apis: true
+// this property is a no-op for java_library
+// TODO (b/215379393): Remove this allowlist
+var (
+	aospPlatformApiAllowlist = map[string]bool{
+		"adservices-test-scenarios":                         true,
+		"aidl-cpp-java-test-interface-java":                 true,
+		"aidl-test-extras-java":                             true,
+		"aidl-test-interface-java":                          true,
+		"aidl-test-interface-permission-java":               true,
+		"aidl_test_java_client_permission":                  true,
+		"aidl_test_java_client_sdk1":                        true,
+		"aidl_test_java_client_sdk29":                       true,
+		"aidl_test_java_client":                             true,
+		"aidl_test_java_service_permission":                 true,
+		"aidl_test_java_service_sdk1":                       true,
+		"aidl_test_java_service_sdk29":                      true,
+		"aidl_test_java_service":                            true,
+		"aidl_test_loggable_interface-java":                 true,
+		"aidl_test_nonvintf_parcelable-V1-java":             true,
+		"aidl_test_nonvintf_parcelable-V2-java":             true,
+		"aidl_test_unstable_parcelable-java":                true,
+		"aidl_test_vintf_parcelable-V1-java":                true,
+		"aidl_test_vintf_parcelable-V2-java":                true,
+		"android.aidl.test.trunk-V1-java":                   true,
+		"android.aidl.test.trunk-V2-java":                   true,
+		"android.frameworks.location.altitude-V1-java":      true,
+		"android.frameworks.location.altitude-V2-java":      true,
+		"android.frameworks.stats-V1-java":                  true,
+		"android.frameworks.stats-V2-java":                  true,
+		"android.frameworks.stats-V3-java":                  true,
+		"android.hardware.authsecret-V1-java":               true,
+		"android.hardware.authsecret-V2-java":               true,
+		"android.hardware.biometrics.common-V1-java":        true,
+		"android.hardware.biometrics.common-V2-java":        true,
+		"android.hardware.biometrics.common-V3-java":        true,
+		"android.hardware.biometrics.common-V4-java":        true,
+		"android.hardware.biometrics.face-V1-java":          true,
+		"android.hardware.biometrics.face-V2-java":          true,
+		"android.hardware.biometrics.face-V3-java":          true,
+		"android.hardware.biometrics.face-V4-java":          true,
+		"android.hardware.biometrics.fingerprint-V1-java":   true,
+		"android.hardware.biometrics.fingerprint-V2-java":   true,
+		"android.hardware.biometrics.fingerprint-V3-java":   true,
+		"android.hardware.biometrics.fingerprint-V4-java":   true,
+		"android.hardware.bluetooth.lmp_event-V1-java":      true,
+		"android.hardware.confirmationui-V1-java":           true,
+		"android.hardware.confirmationui-V2-java":           true,
+		"android.hardware.gatekeeper-V1-java":               true,
+		"android.hardware.gatekeeper-V2-java":               true,
+		"android.hardware.gnss-V1-java":                     true,
+		"android.hardware.gnss-V2-java":                     true,
+		"android.hardware.gnss-V3-java":                     true,
+		"android.hardware.gnss-V4-java":                     true,
+		"android.hardware.graphics.common-V1-java":          true,
+		"android.hardware.graphics.common-V2-java":          true,
+		"android.hardware.graphics.common-V3-java":          true,
+		"android.hardware.graphics.common-V4-java":          true,
+		"android.hardware.graphics.common-V5-java":          true,
+		"android.hardware.identity-V1-java":                 true,
+		"android.hardware.identity-V2-java":                 true,
+		"android.hardware.identity-V3-java":                 true,
+		"android.hardware.identity-V4-java":                 true,
+		"android.hardware.identity-V5-java":                 true,
+		"android.hardware.identity-V6-java":                 true,
+		"android.hardware.keymaster-V1-java":                true,
+		"android.hardware.keymaster-V2-java":                true,
+		"android.hardware.keymaster-V3-java":                true,
+		"android.hardware.keymaster-V4-java":                true,
+		"android.hardware.keymaster-V5-java":                true,
+		"android.hardware.oemlock-V1-java":                  true,
+		"android.hardware.oemlock-V2-java":                  true,
+		"android.hardware.power.stats-V1-java":              true,
+		"android.hardware.power.stats-V2-java":              true,
+		"android.hardware.power.stats-V3-java":              true,
+		"android.hardware.power-V1-java":                    true,
+		"android.hardware.power-V2-java":                    true,
+		"android.hardware.power-V3-java":                    true,
+		"android.hardware.power-V4-java":                    true,
+		"android.hardware.power-V5-java":                    true,
+		"android.hardware.rebootescrow-V1-java":             true,
+		"android.hardware.rebootescrow-V2-java":             true,
+		"android.hardware.security.authgraph-V1-java":       true,
+		"android.hardware.security.keymint-V1-java":         true,
+		"android.hardware.security.keymint-V2-java":         true,
+		"android.hardware.security.keymint-V3-java":         true,
+		"android.hardware.security.keymint-V4-java":         true,
+		"android.hardware.security.secureclock-V1-java":     true,
+		"android.hardware.security.secureclock-V2-java":     true,
+		"android.hardware.thermal-V1-java":                  true,
+		"android.hardware.thermal-V2-java":                  true,
+		"android.hardware.threadnetwork-V1-java":            true,
+		"android.hardware.weaver-V1-java":                   true,
+		"android.hardware.weaver-V2-java":                   true,
+		"android.hardware.weaver-V3-java":                   true,
+		"android.security.attestationmanager-java":          true,
+		"android.security.authorization-java":               true,
+		"android.security.compat-java":                      true,
+		"android.security.legacykeystore-java":              true,
+		"android.security.maintenance-java":                 true,
+		"android.security.metrics-java":                     true,
+		"android.system.keystore2-V1-java":                  true,
+		"android.system.keystore2-V2-java":                  true,
+		"android.system.keystore2-V3-java":                  true,
+		"android.system.keystore2-V4-java":                  true,
+		"binderReadParcelIface-java":                        true,
+		"binderRecordReplayTestIface-java":                  true,
+		"car-experimental-api-static-lib":                   true,
+		"collector-device-lib-platform":                     true,
+		"com.android.car.oem":                               true,
+		"com.google.hardware.pixel.display-V10-java":        true,
+		"com.google.hardware.pixel.display-V1-java":         true,
+		"com.google.hardware.pixel.display-V2-java":         true,
+		"com.google.hardware.pixel.display-V3-java":         true,
+		"com.google.hardware.pixel.display-V4-java":         true,
+		"com.google.hardware.pixel.display-V5-java":         true,
+		"com.google.hardware.pixel.display-V6-java":         true,
+		"com.google.hardware.pixel.display-V7-java":         true,
+		"com.google.hardware.pixel.display-V8-java":         true,
+		"com.google.hardware.pixel.display-V9-java":         true,
+		"conscrypt-support":                                 true,
+		"cts-keystore-test-util":                            true,
+		"cts-keystore-user-auth-helper-library":             true,
+		"ctsmediautil":                                      true,
+		"CtsNetTestsNonUpdatableLib":                        true,
+		"DpmWrapper":                                        true,
+		"flickerlib-apphelpers":                             true,
+		"flickerlib-helpers":                                true,
+		"flickerlib-parsers":                                true,
+		"flickerlib":                                        true,
+		"hardware.google.bluetooth.ccc-V1-java":             true,
+		"hardware.google.bluetooth.sar-V1-java":             true,
+		"monet":                                             true,
+		"pixel-power-ext-V1-java":                           true,
+		"pixel-power-ext-V2-java":                           true,
+		"pixel_stateresidency_provider_aidl_interface-java": true,
+		"pixel-thermal-ext-V1-java":                         true,
+		"protolog-lib":                                      true,
+		"RkpRegistrationCheck":                              true,
+		"rotary-service-javastream-protos":                  true,
+		"service_based_camera_extensions":                   true,
+		"statsd-helper-test":                                true,
+		"statsd-helper":                                     true,
+		"test-piece-2-V1-java":                              true,
+		"test-piece-2-V2-java":                              true,
+		"test-piece-3-V1-java":                              true,
+		"test-piece-3-V2-java":                              true,
+		"test-piece-3-V3-java":                              true,
+		"test-piece-4-V1-java":                              true,
+		"test-piece-4-V2-java":                              true,
+		"test-root-package-V1-java":                         true,
+		"test-root-package-V2-java":                         true,
+		"test-root-package-V3-java":                         true,
+		"test-root-package-V4-java":                         true,
+		"testServiceIface-java":                             true,
+		"wm-flicker-common-app-helpers":                     true,
+		"wm-flicker-common-assertions":                      true,
+		"wm-shell-flicker-utils":                            true,
+		"wycheproof-keystore":                               true,
+	}
 
+	// Union of aosp and internal allowlists
+	PlatformApiAllowlist = map[string]bool{}
+)
+
+func init() {
+	for k, v := range aospPlatformApiAllowlist {
+		PlatformApiAllowlist[k] = v
+	}
+}
+
+func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.provideHiddenAPIPropertyInfo(ctx)
 
 	j.sdkVersion = j.SdkVersion(ctx)
 	j.minSdkVersion = j.MinSdkVersion(ctx)
 	j.maxSdkVersion = j.MaxSdkVersion(ctx)
 
+	// SdkLibrary.GenerateAndroidBuildActions(ctx) sets the stubsLinkType to Unknown.
+	// If the stubsLinkType has already been set to Unknown, the stubsLinkType should
+	// not be overridden.
+	if j.stubsLinkType != Unknown {
+		if proptools.Bool(j.properties.Is_stubs_module) {
+			j.stubsLinkType = Stubs
+		} else {
+			j.stubsLinkType = Implementation
+		}
+	}
+
 	j.stem = proptools.StringDefault(j.overridableDeviceProperties.Stem, ctx.ModuleName())
 
 	proguardSpecInfo := j.collectProguardSpecInfo(ctx)
@@ -719,6 +922,9 @@
 		setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 		j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 		j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
+		if j.usesLibrary.shouldDisableDexpreopt {
+			j.dexpreopter.disableDexpreopt()
+		}
 	}
 	j.compile(ctx, nil, nil, nil)
 
@@ -1666,6 +1872,10 @@
 	dexJarFile OptionalDexJarPath
 
 	validationPaths android.Paths
+
+	stubsType StubsType
+
+	aconfigProtoFiles android.Paths
 }
 
 type JavaApiLibraryProperties struct {
@@ -1707,6 +1917,20 @@
 	// in sync with the source Java files. However, the environment variable
 	// DISABLE_STUB_VALIDATION has precedence over this property.
 	Enable_validation *bool
+
+	// Type of stubs the module should generate. Must be one of "everything", "runtime" or
+	// "exportable". Defaults to "everything".
+	// - "everything" stubs include all non-flagged apis and flagged apis, regardless of the state
+	// of the flag.
+	// - "runtime" stubs include all non-flagged apis and flagged apis that are ENABLED or
+	// READ_WRITE, and all other flagged apis are stripped.
+	// - "exportable" stubs include all non-flagged apis and flagged apis that are ENABLED and
+	// READ_ONLY, and all other flagged apis are stripped.
+	Stubs_type *string
+
+	// List of aconfig_declarations module names that the stubs generated in this module
+	// depend on.
+	Aconfig_declarations []string
 }
 
 func ApiLibraryFactory() android.Module {
@@ -1870,6 +2094,9 @@
 	if al.properties.System_modules != nil {
 		ctx.AddVariationDependencies(nil, systemModulesTag, String(al.properties.System_modules))
 	}
+	for _, aconfigDeclarationsName := range al.properties.Aconfig_declarations {
+		ctx.AddDependency(ctx.Module(), aconfigDeclarationTag, aconfigDeclarationsName)
+	}
 }
 
 // Map where key is the api scope name and value is the int value
@@ -1890,7 +2117,23 @@
 	return srcFilesInfo
 }
 
+var validstubsType = []StubsType{Everything, Runtime, Exportable}
+
+func (al *ApiLibrary) validateProperties(ctx android.ModuleContext) {
+	if al.properties.Stubs_type == nil {
+		ctx.ModuleErrorf("java_api_library module type must specify stubs_type property.")
+	} else {
+		al.stubsType = StringToStubsType(proptools.String(al.properties.Stubs_type))
+	}
+
+	if !android.InList(al.stubsType, validstubsType) {
+		ctx.PropertyErrorf("stubs_type", "%s is not a valid stubs_type property value. "+
+			"Must be one of %s.", proptools.String(al.properties.Stubs_type), validstubsType)
+	}
+}
+
 func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	al.validateProperties(ctx)
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 
@@ -1934,6 +2177,18 @@
 			if currentApiTimestampProvider, ok := dep.(currentApiTimestampProvider); ok {
 				al.validationPaths = append(al.validationPaths, currentApiTimestampProvider.CurrentApiTimestamp())
 			}
+		case aconfigDeclarationTag:
+			if provider, ok := android.OtherModuleProvider(ctx, dep, android.AconfigDeclarationsProviderKey); ok {
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.IntermediateCacheOutputPath)
+			} else if provider, ok := android.OtherModuleProvider(ctx, dep, android.CodegenInfoProvider); ok {
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.IntermediateCacheOutputPaths...)
+			} else {
+				ctx.ModuleErrorf("Only aconfig_declarations and aconfig_declarations_group "+
+					"module type is allowed for flags_packages property, but %s is neither "+
+					"of these supported module types",
+					dep.Name(),
+				)
+			}
 		}
 	})
 
@@ -1959,6 +2214,8 @@
 
 	al.addValidation(ctx, cmd, al.validationPaths)
 
+	generateRevertAnnotationArgs(ctx, cmd, al.stubsType, al.aconfigProtoFiles)
+
 	al.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar")
 	al.stubsJarWithoutStaticLibs = android.PathForModuleOut(ctx, "metalava", "stubs.jar")
 	al.stubsJar = android.PathForModuleOut(ctx, ctx.ModuleName(), fmt.Sprintf("%s.jar", ctx.ModuleName()))
@@ -2018,6 +2275,7 @@
 		ImplementationAndResourcesJars: android.PathsIfNonNil(al.stubsJar),
 		ImplementationJars:             android.PathsIfNonNil(al.stubsJar),
 		AidlIncludeDirs:                android.Paths{},
+		StubsLinkType:                  Stubs,
 		// No aconfig libraries on api libraries
 	})
 }
@@ -2102,6 +2360,9 @@
 	// The name is the undecorated name of the java_sdk_library as it appears in the blueprint file
 	// (without any prebuilt_ prefix)
 	Created_by_java_sdk_library_name *string `blueprint:"mutated"`
+
+	// Property signifying whether the module provides stubs jar or not.
+	Is_stubs_module *bool
 }
 
 type Import struct {
@@ -2132,6 +2393,8 @@
 
 	sdkVersion    android.SdkSpec
 	minSdkVersion android.ApiLevel
+
+	stubsLinkType StubsLinkType
 }
 
 var _ PermittedPackagesForUpdatableBootJars = (*Import)(nil)
@@ -2226,6 +2489,12 @@
 	if ctx.Windows() {
 		j.HideFromMake()
 	}
+
+	if proptools.Bool(j.properties.Is_stubs_module) {
+		j.stubsLinkType = Stubs
+	} else {
+		j.stubsLinkType = Implementation
+	}
 }
 
 func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -2359,6 +2628,7 @@
 		ImplementationAndResourcesJars: android.PathsIfNonNil(j.combinedClasspathFile),
 		ImplementationJars:             android.PathsIfNonNil(j.combinedClasspathFile),
 		AidlIncludeDirs:                j.exportAidlIncludeDirs,
+		StubsLinkType:                  j.stubsLinkType,
 		// TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
 	})
 }
diff --git a/java/java_test.go b/java/java_test.go
index 42301d8..9a4f085 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1756,6 +1756,7 @@
 			name: "bar",
 			api_surface: "public",
 			api_contributions: ["foo"],
+			stubs_type: "everything",
 		}
 	`)
 }
@@ -1792,12 +1793,14 @@
 			name: "bar1",
 			api_surface: "public",
 			api_contributions: ["foo1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
 			name: "bar2",
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
+			stubs_type: "everything",
 		}
 	`)
 
@@ -1885,12 +1888,14 @@
 			name: "bar1",
 			api_surface: "public",
 			api_contributions: ["foo1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
 			name: "bar2",
 			api_surface: "public",
 			defaults:["baz1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
@@ -1898,6 +1903,7 @@
 			api_surface: "system",
 			defaults:["baz1", "baz2"],
 			api_contributions: ["foo4"],
+			stubs_type: "everything",
 		}
 	`)
 
@@ -1962,12 +1968,14 @@
 			name: "bar1",
 			api_surface: "public",
 			api_contributions: ["foo1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
 			name: "bar2",
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
+			stubs_type: "everything",
 		}
 	`)
 
@@ -2044,6 +2052,7 @@
 			api_surface: "public",
 			api_contributions: ["foo1"],
 			libs: ["lib1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
@@ -2051,6 +2060,7 @@
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
 			libs: ["lib1", "lib2", "bar1"],
+			stubs_type: "everything",
 		}
 	`)
 
@@ -2130,6 +2140,7 @@
 			api_surface: "public",
 			api_contributions: ["foo1"],
 			static_libs: ["lib1"],
+			stubs_type: "everything",
 		}
 
 		java_api_library {
@@ -2137,6 +2148,7 @@
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
 			static_libs: ["lib1", "lib2", "bar1"],
+			stubs_type: "everything",
 		}
 	`)
 
@@ -2184,6 +2196,7 @@
 		name: "lib1",
 		api_surface: "public",
 		api_contributions: ["foo1", "foo2"],
+		stubs_type: "everything",
 	}
 	`
 
@@ -2207,6 +2220,7 @@
 			api_surface: "public",
 			api_contributions: ["foo1"],
 			full_api_surface_stub: "lib1",
+			stubs_type: "everything",
 		}
 	`)
 
@@ -2368,6 +2382,7 @@
 		java_api_library {
 			name: "foo",
 			api_contributions: ["bar"],
+			stubs_type: "everything",
 		}
 		java_api_contribution_import {
 			name: "bar",
@@ -2394,6 +2409,7 @@
 				"module-lib-api-stubs-docs-non-updatable.api.contribution",
 				"api-stubs-docs-non-updatable.api.contribution",
 			],
+			stubs_type: "everything",
 		}
 	`)
 	m := ctx.ModuleForTests("foo", "android_common")
@@ -2466,6 +2482,7 @@
 				"api-stubs-docs-non-updatable.api.contribution",
 			],
 			enable_validation: true,
+			stubs_type: "everything",
 		}
 		java_api_library {
 			name: "bar",
@@ -2473,6 +2490,7 @@
 				"api-stubs-docs-non-updatable.api.contribution",
 			],
 			enable_validation: false,
+			stubs_type: "everything",
 		}
 	`)
 
@@ -2624,3 +2642,118 @@
 		android.AssertStringEquals(t, "unexpected LOCAL_MODULE", "bar", entries.EntryMap["LOCAL_MODULE"][0])
 	}
 }
+
+func TestMultiplePlatformCompatConfigPrebuilts(t *testing.T) {
+	bp := `
+		// multiple variations of platform_compat_config
+		// source
+		platform_compat_config {
+			name: "myconfig",
+		}
+		// prebuilt "v1"
+		prebuilt_platform_compat_config {
+			name: "myconfig",
+			metadata: "myconfig.xml",
+		}
+		// prebuilt "v2"
+		prebuilt_platform_compat_config {
+			name: "myconfig.v2",
+			source_module_name: "myconfig", // without source_module_name, the singleton will merge two .xml files
+			metadata: "myconfig.v2.xml",
+		}
+
+		// selectors
+		apex_contributions {
+			name: "myapex_contributions",
+			contents: ["%v"],
+		}
+	`
+	testCases := []struct {
+		desc                            string
+		selectedDependencyName          string
+		expectedPlatformCompatConfigXml string
+	}{
+		{
+			desc:                            "Source platform_compat_config is selected using apex_contributions",
+			selectedDependencyName:          "myconfig",
+			expectedPlatformCompatConfigXml: "out/soong/.intermediates/myconfig/android_common/myconfig_meta.xml",
+		},
+		{
+			desc:                            "Prebuilt platform_compat_config v1 is selected using apex_contributions",
+			selectedDependencyName:          "prebuilt_myconfig",
+			expectedPlatformCompatConfigXml: "myconfig.xml",
+		},
+		{
+			desc:                            "Prebuilt platform_compat_config v2 is selected using apex_contributions",
+			selectedDependencyName:          "prebuilt_myconfig.v2",
+			expectedPlatformCompatConfigXml: "myconfig.v2.xml",
+		},
+	}
+
+	for _, tc := range testCases {
+		ctx := android.GroupFixturePreparers(
+			prepareForJavaTest,
+			PrepareForTestWithPlatformCompatConfig,
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.BuildFlags = map[string]string{
+					"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "myapex_contributions",
+				}
+			}),
+		).RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName))
+
+		mergedGlobalConfig := ctx.SingletonForTests("platform_compat_config_singleton").Output("compat_config/merged_compat_config.xml")
+		android.AssertIntEquals(t, "The merged compat config file should only have a single dependency", 1, len(mergedGlobalConfig.Implicits))
+		android.AssertStringEquals(t, "The merged compat config file is missing the appropriate platform compat config", mergedGlobalConfig.Implicits[0].String(), tc.expectedPlatformCompatConfigXml)
+	}
+}
+
+func TestApiLibraryAconfigDeclarations(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		}),
+		android.FixtureMergeMockFs(map[string][]byte{
+			"a/A.java":      nil,
+			"a/current.txt": nil,
+			"a/removed.txt": nil,
+		}),
+	).RunTestWithBp(t, `
+	aconfig_declarations {
+		name: "bar",
+		package: "com.example.package",
+		srcs: [
+			"bar.aconfig",
+		],
+	}
+	java_api_contribution {
+		name: "baz",
+		api_file: "a/current.txt",
+		api_surface: "public",
+	}
+	java_api_library {
+		name: "foo",
+		api_surface: "public",
+		api_contributions: [
+			"baz",
+		],
+		aconfig_declarations: [
+			"bar",
+		],
+		stubs_type: "exportable",
+		enable_validation: false,
+	}
+	`)
+
+	// Check if java_api_library depends on aconfig_declarations
+	android.AssertBoolEquals(t, "foo expected to depend on bar",
+		CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
+
+	m := result.ModuleForTests("foo", "android_common")
+	android.AssertStringDoesContain(t, "foo generates revert annotations file",
+		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
+
+	// revert-annotations.txt passed to exportable stubs generation metalava command
+	manifest := m.Output("metalava.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")
+}
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 2197304..2fc6c02 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -184,6 +185,11 @@
 
 type prebuiltCompatConfigProperties struct {
 	Metadata *string `android:"path"`
+
+	// 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 (module *prebuiltCompatConfigModule) Prebuilt() *android.Prebuilt {
@@ -198,6 +204,10 @@
 	return module.metadataFile
 }
 
+func (module *prebuiltCompatConfigModule) BaseModuleName() string {
+	return proptools.StringDefault(module.properties.Source_module_name, module.ModuleBase.Name())
+}
+
 var _ platformCompatConfigMetadataProvider = (*prebuiltCompatConfigModule)(nil)
 
 func (module *prebuiltCompatConfigModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/java/ravenwood.go b/java/ravenwood.go
new file mode 100644
index 0000000..908619d
--- /dev/null
+++ b/java/ravenwood.go
@@ -0,0 +1,278 @@
+// Copyright 2023 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 java
+
+import (
+	"android/soong/android"
+	"android/soong/tradefed"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	RegisterRavenwoodBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterRavenwoodBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_ravenwood_test", ravenwoodTestFactory)
+	ctx.RegisterModuleType("android_ravenwood_libgroup", ravenwoodLibgroupFactory)
+}
+
+var ravenwoodLibContentTag = dependencyTag{name: "ravenwoodlibcontent"}
+var ravenwoodUtilsTag = dependencyTag{name: "ravenwoodutils"}
+var ravenwoodRuntimeTag = dependencyTag{name: "ravenwoodruntime"}
+
+const ravenwoodUtilsName = "ravenwood-utils"
+const ravenwoodRuntimeName = "ravenwood-runtime"
+
+type ravenwoodLibgroupJniDepProviderInfo struct {
+	// All the jni_libs module names with transient dependencies.
+	names map[string]bool
+}
+
+var ravenwoodLibgroupJniDepProvider = blueprint.NewProvider[ravenwoodLibgroupJniDepProviderInfo]()
+
+func getLibPath(archType android.ArchType) string {
+	if archType.Multilib == "lib64" {
+		return "lib64"
+	}
+	return "lib"
+}
+
+type ravenwoodTestProperties struct {
+	Jni_libs []string
+}
+
+type ravenwoodTest struct {
+	Library
+
+	ravenwoodTestProperties ravenwoodTestProperties
+
+	testProperties testProperties
+	testConfig     android.Path
+
+	forceOSType   android.OsType
+	forceArchType android.ArchType
+}
+
+func ravenwoodTestFactory() android.Module {
+	module := &ravenwoodTest{}
+
+	module.addHostAndDeviceProperties()
+	module.AddProperties(&module.testProperties, &module.ravenwoodTestProperties)
+
+	module.Module.dexpreopter.isTest = true
+	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+
+	module.testProperties.Test_suites = []string{
+		"general-tests",
+		"ravenwood-tests",
+	}
+	module.testProperties.Test_options.Unit_test = proptools.BoolPtr(false)
+
+	InitJavaModule(module, android.DeviceSupported)
+	android.InitDefaultableModule(module)
+
+	return module
+}
+
+func (r *ravenwoodTest) InstallInTestcases() bool { return true }
+func (r *ravenwoodTest) InstallForceOS() (*android.OsType, *android.ArchType) {
+	return &r.forceOSType, &r.forceArchType
+}
+func (r *ravenwoodTest) TestSuites() []string {
+	return r.testProperties.Test_suites
+}
+
+func (r *ravenwoodTest) DepsMutator(ctx android.BottomUpMutatorContext) {
+	r.Library.DepsMutator(ctx)
+
+	// Generically depend on the runtime so that it's installed together with us
+	ctx.AddVariationDependencies(nil, ravenwoodRuntimeTag, ravenwoodRuntimeName)
+
+	// Directly depend on any utils so that we link against them
+	utils := ctx.AddVariationDependencies(nil, ravenwoodUtilsTag, ravenwoodUtilsName)[0]
+	if utils != nil {
+		for _, lib := range utils.(*ravenwoodLibgroup).ravenwoodLibgroupProperties.Libs {
+			ctx.AddVariationDependencies(nil, libTag, lib)
+		}
+	}
+
+	// Add jni libs
+	for _, lib := range r.ravenwoodTestProperties.Jni_libs {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
+	}
+}
+
+func (r *ravenwoodTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	r.forceOSType = ctx.Config().BuildOS
+	r.forceArchType = ctx.Config().BuildArch
+
+	r.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
+		TestConfigProp:         r.testProperties.Test_config,
+		TestConfigTemplateProp: r.testProperties.Test_config_template,
+		TestSuites:             r.testProperties.Test_suites,
+		AutoGenConfig:          r.testProperties.Auto_gen_config,
+		DeviceTemplate:         "${RavenwoodTestConfigTemplate}",
+		HostTemplate:           "${RavenwoodTestConfigTemplate}",
+	})
+
+	r.Library.GenerateAndroidBuildActions(ctx)
+
+	// Start by depending on all files installed by dependencies
+	var installDeps android.InstallPaths
+
+	// All JNI libraries included in the runtime
+	var runtimeJniModuleNames map[string]bool
+
+	if utils := ctx.GetDirectDepsWithTag(ravenwoodUtilsTag)[0]; utils != nil {
+		for _, installFile := range utils.FilesToInstall() {
+			installDeps = append(installDeps, installFile)
+		}
+		jniDeps, ok := android.OtherModuleProvider(ctx, utils, ravenwoodLibgroupJniDepProvider)
+		if ok {
+			runtimeJniModuleNames = jniDeps.names
+		}
+	}
+
+	if runtime := ctx.GetDirectDepsWithTag(ravenwoodRuntimeTag)[0]; runtime != nil {
+		for _, installFile := range runtime.FilesToInstall() {
+			installDeps = append(installDeps, installFile)
+		}
+		jniDeps, ok := android.OtherModuleProvider(ctx, runtime, ravenwoodLibgroupJniDepProvider)
+		if ok {
+			runtimeJniModuleNames = jniDeps.names
+		}
+	}
+
+	// Also remember what JNI libs are in the runtime.
+
+	// Also depend on our config
+	installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
+	installConfig := ctx.InstallFile(installPath, ctx.ModuleName()+".config", r.testConfig)
+	installDeps = append(installDeps, installConfig)
+
+	// Depend on the JNI libraries, but don't install the ones that the runtime already
+	// contains.
+	soInstallPath := installPath.Join(ctx, getLibPath(r.forceArchType))
+	for _, jniLib := range collectTransitiveJniDeps(ctx) {
+		if _, ok := runtimeJniModuleNames[jniLib.name]; ok {
+			continue // Runtime already includes it.
+		}
+		installJni := ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path)
+		installDeps = append(installDeps, installJni)
+	}
+
+	// Install our JAR with all dependencies
+	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
+}
+
+func (r *ravenwoodTest) AndroidMkEntries() []android.AndroidMkEntries {
+	entriesList := r.Library.AndroidMkEntries()
+	entries := &entriesList[0]
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+			entries.AddStrings("LOCAL_COMPATIBILITY_SUITE",
+				"general-tests", "ravenwood-tests")
+			if r.testConfig != nil {
+				entries.SetPath("LOCAL_FULL_TEST_CONFIG", r.testConfig)
+			}
+		})
+	return entriesList
+}
+
+type ravenwoodLibgroupProperties struct {
+	Libs []string
+
+	Jni_libs []string
+}
+
+type ravenwoodLibgroup struct {
+	android.ModuleBase
+
+	ravenwoodLibgroupProperties ravenwoodLibgroupProperties
+
+	forceOSType   android.OsType
+	forceArchType android.ArchType
+}
+
+func ravenwoodLibgroupFactory() android.Module {
+	module := &ravenwoodLibgroup{}
+	module.AddProperties(&module.ravenwoodLibgroupProperties)
+
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (r *ravenwoodLibgroup) InstallInTestcases() bool { return true }
+func (r *ravenwoodLibgroup) InstallForceOS() (*android.OsType, *android.ArchType) {
+	return &r.forceOSType, &r.forceArchType
+}
+func (r *ravenwoodLibgroup) TestSuites() []string {
+	return []string{
+		"general-tests",
+		"ravenwood-tests",
+	}
+}
+
+func (r *ravenwoodLibgroup) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// Always depends on our underlying libs
+	for _, lib := range r.ravenwoodLibgroupProperties.Libs {
+		ctx.AddVariationDependencies(nil, ravenwoodLibContentTag, lib)
+	}
+	for _, lib := range r.ravenwoodLibgroupProperties.Jni_libs {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
+	}
+}
+
+func (r *ravenwoodLibgroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	r.forceOSType = ctx.Config().BuildOS
+	r.forceArchType = ctx.Config().BuildArch
+
+	// Collect the JNI dependencies, including the transitive deps.
+	jniDepNames := make(map[string]bool)
+	jniLibs := collectTransitiveJniDeps(ctx)
+
+	for _, jni := range jniLibs {
+		jniDepNames[jni.name] = true
+	}
+	android.SetProvider(ctx, ravenwoodLibgroupJniDepProvider, ravenwoodLibgroupJniDepProviderInfo{
+		names: jniDepNames,
+	})
+
+	// Install our runtime into expected location for packaging
+	installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
+	for _, lib := range r.ravenwoodLibgroupProperties.Libs {
+		libModule := ctx.GetDirectDepWithTag(lib, ravenwoodLibContentTag)
+		libJar := android.OutputFileForModule(ctx, libModule, "")
+		ctx.InstallFile(installPath, lib+".jar", libJar)
+	}
+	soInstallPath := android.PathForModuleInstall(ctx, r.BaseModuleName()).Join(ctx, getLibPath(r.forceArchType))
+
+	for _, jniLib := range jniLibs {
+		ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path)
+	}
+
+	// Normal build should perform install steps
+	ctx.Phony(r.BaseModuleName(), android.PathForPhony(ctx, r.BaseModuleName()+"-install"))
+}
+
+// collectTransitiveJniDeps returns all JNI dependencies, including transitive
+// ones, including NDK / stub libs. (Because Ravenwood has no "preinstalled" libraries)
+func collectTransitiveJniDeps(ctx android.ModuleContext) []jniLib {
+	libs, _ := collectJniDeps(ctx, true, false, nil)
+	return libs
+}
diff --git a/java/ravenwood_test.go b/java/ravenwood_test.go
new file mode 100644
index 0000000..5961264
--- /dev/null
+++ b/java/ravenwood_test.go
@@ -0,0 +1,186 @@
+// Copyright 2022 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 java
+
+import (
+	"runtime"
+	"testing"
+
+	"android/soong/android"
+)
+
+var prepareRavenwoodRuntime = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		RegisterRavenwoodBuildComponents(ctx)
+	}),
+	android.FixtureAddTextFile("ravenwood/Android.bp", `
+		cc_library_shared {
+			name: "ravenwood-runtime-jni1",
+			host_supported: true,
+			srcs: ["jni.cpp"],
+		}
+		cc_library_shared {
+			name: "ravenwood-runtime-jni2",
+			host_supported: true,
+			srcs: ["jni.cpp"],
+			stem: "libred",
+			shared_libs: [
+				"ravenwood-runtime-jni3",
+			],
+		}
+		cc_library_shared {
+			name: "ravenwood-runtime-jni3",
+			host_supported: true,
+			srcs: ["jni.cpp"],
+		}
+		java_library_static {
+			name: "framework-minus-apex.ravenwood",
+			srcs: ["Framework.java"],
+		}
+		java_library_static {
+			name: "framework-services.ravenwood",
+			srcs: ["Services.java"],
+		}
+		java_library_static {
+			name: "framework-rules.ravenwood",
+			srcs: ["Rules.java"],
+		}
+		android_ravenwood_libgroup {
+			name: "ravenwood-runtime",
+			libs: [
+				"framework-minus-apex.ravenwood",
+				"framework-services.ravenwood",
+			],
+			jni_libs: [
+				"ravenwood-runtime-jni1",
+				"ravenwood-runtime-jni2",
+			],
+		}
+		android_ravenwood_libgroup {
+			name: "ravenwood-utils",
+			libs: [
+				"framework-rules.ravenwood",
+			],
+		}
+	`),
+)
+
+var installPathPrefix = "out/soong/host/linux-x86/testcases"
+
+func TestRavenwoodRuntime(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+
+	ctx := android.GroupFixturePreparers(
+		PrepareForIntegrationTestWithJava,
+		prepareRavenwoodRuntime,
+	).RunTest(t)
+
+	// Verify that our runtime depends on underlying libs
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-runtime", "android_common", "framework-minus-apex.ravenwood")
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-runtime", "android_common", "framework-services.ravenwood")
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-runtime", "android_common", "ravenwood-runtime-jni")
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-utils", "android_common", "framework-rules.ravenwood")
+
+	// Verify that we've emitted artifacts in expected location
+	runtime := ctx.ModuleForTests("ravenwood-runtime", "android_common")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/framework-minus-apex.ravenwood.jar")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/framework-services.ravenwood.jar")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni1.so")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/libred.so")
+	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni3.so")
+	utils := ctx.ModuleForTests("ravenwood-utils", "android_common")
+	utils.Output(installPathPrefix + "/ravenwood-utils/framework-rules.ravenwood.jar")
+}
+
+func TestRavenwoodTest(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+
+	ctx := android.GroupFixturePreparers(
+		PrepareForIntegrationTestWithJava,
+		prepareRavenwoodRuntime,
+	).RunTestWithBp(t, `
+	cc_library_shared {
+		name: "jni-lib1",
+		host_supported: true,
+		srcs: ["jni.cpp"],
+	}
+	cc_library_shared {
+		name: "jni-lib2",
+		host_supported: true,
+		srcs: ["jni.cpp"],
+		stem: "libblue",
+		shared_libs: [
+			"jni-lib3",
+		],
+	}
+	cc_library_shared {
+		name: "jni-lib3",
+		host_supported: true,
+		srcs: ["jni.cpp"],
+		stem: "libpink",
+	}
+	android_ravenwood_test {
+			name: "ravenwood-test",
+			srcs: ["Test.java"],
+			jni_libs: [
+				"jni-lib1",
+				"jni-lib2",
+				"ravenwood-runtime-jni2",
+			],
+			sdk_version: "test_current",
+		}
+	`)
+
+	// Verify that our test depends on underlying libs
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-test", "android_common", "ravenwood-buildtime")
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-test", "android_common", "ravenwood-utils")
+	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-test", "android_common", "jni-lib")
+
+	module := ctx.ModuleForTests("ravenwood-test", "android_common")
+	classpath := module.Rule("javac").Args["classpath"]
+
+	// Verify that we're linking against test_current
+	android.AssertStringDoesContain(t, "classpath", classpath, "android_test_stubs_current.jar")
+	// Verify that we're linking against utils
+	android.AssertStringDoesContain(t, "classpath", classpath, "framework-rules.ravenwood.jar")
+	// Verify that we're *NOT* linking against runtime
+	android.AssertStringDoesNotContain(t, "classpath", classpath, "framework-minus-apex.ravenwood.jar")
+	android.AssertStringDoesNotContain(t, "classpath", classpath, "framework-services.ravenwood.jar")
+
+	// Verify that we've emitted test artifacts in expected location
+	outputJar := module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.jar")
+	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.config")
+	module.Output(installPathPrefix + "/ravenwood-test/lib64/jni-lib1.so")
+	module.Output(installPathPrefix + "/ravenwood-test/lib64/libblue.so")
+	module.Output(installPathPrefix + "/ravenwood-test/lib64/libpink.so")
+
+	// ravenwood-runtime*.so are included in the runtime, so it shouldn't be emitted.
+	for _, o := range module.AllOutputs() {
+		android.AssertStringDoesNotContain(t, "runtime libs shouldn't be included", o, "/ravenwood-test/lib64/ravenwood-runtime")
+	}
+
+	// Verify that we're going to install underlying libs
+	orderOnly := outputJar.OrderOnly.Strings()
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-runtime/framework-minus-apex.ravenwood.jar")
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-runtime/framework-services.ravenwood.jar")
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-runtime/lib64/ravenwood-runtime-jni1.so")
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-runtime/lib64/libred.so")
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-runtime/lib64/ravenwood-runtime-jni3.so")
+	android.AssertStringListContains(t, "orderOnly", orderOnly, installPathPrefix+"/ravenwood-utils/framework-rules.ravenwood.jar")
+}
diff --git a/java/sdk.go b/java/sdk.go
index 81be991..3591ccd 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -33,13 +33,26 @@
 var nonUpdatableFrameworkAidlPathKey = android.NewOnceKey("nonUpdatableFrameworkAidlPathKey")
 var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
 
-func UseApiFingerprint(ctx android.BaseModuleContext) bool {
-	if ctx.Config().UnbundledBuild() &&
-		!ctx.Config().AlwaysUsePrebuiltSdks() &&
-		ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT") {
-		return true
+func UseApiFingerprint(ctx android.BaseModuleContext) (useApiFingerprint bool, fingerprintSdkVersion string, fingerprintDeps android.OutputPath) {
+	if ctx.Config().UnbundledBuild() && !ctx.Config().AlwaysUsePrebuiltSdks() {
+		apiFingerprintTrue := ctx.Config().IsEnvTrue("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT")
+		dessertShaIsSet := ctx.Config().Getenv("UNBUNDLED_BUILD_TARGET_SDK_WITH_DESSERT_SHA") != ""
+
+		// Error when both UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT and UNBUNDLED_BUILD_TARGET_SDK_WITH_DESSERT_SHA are set
+		if apiFingerprintTrue && dessertShaIsSet {
+			ctx.ModuleErrorf("UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true cannot be set alongside with UNBUNDLED_BUILD_TARGET_SDK_WITH_DESSERT_SHA")
+		}
+
+		useApiFingerprint = apiFingerprintTrue || dessertShaIsSet
+		if apiFingerprintTrue {
+			fingerprintSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
+			fingerprintDeps = ApiFingerprintPath(ctx)
+		}
+		if dessertShaIsSet {
+			fingerprintSdkVersion = ctx.Config().Getenv("UNBUNDLED_BUILD_TARGET_SDK_WITH_DESSERT_SHA")
+		}
 	}
-	return false
+	return useApiFingerprint, fingerprintSdkVersion, fingerprintDeps
 }
 
 func defaultJavaLanguageVersion(ctx android.EarlyModuleContext, s android.SdkSpec) javaVersion {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index fbde042..cdd0448 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1572,6 +1572,8 @@
 
 	// Only build an implementation library if required.
 	if module.requiresRuntimeImplementationLibrary() {
+		// stubsLinkType must be set before calling Library.GenerateAndroidBuildActions
+		module.Library.stubsLinkType = Unknown
 		module.Library.GenerateAndroidBuildActions(ctx)
 	}
 
@@ -1797,6 +1799,7 @@
 		Dir     *string
 		Tag     *string
 	}
+	Is_stubs_module *bool
 }
 
 func (module *SdkLibrary) stubsLibraryProps(mctx android.DefaultableHookContext, apiScope *apiScope) libraryProperties {
@@ -1821,6 +1824,7 @@
 	// We compile the stubs for 1.8 in line with the main android.jar stubs, and potential
 	// interop with older developer tools that don't support 1.9.
 	props.Java_version = proptools.StringPtr("1.8")
+	props.Is_stubs_module = proptools.BoolPtr(true)
 
 	return props
 }
@@ -1993,8 +1997,10 @@
 			tag     string
 			pattern string
 		}{
-			{tag: ".api.txt", pattern: "%s.txt"},
-			{tag: ".removed-api.txt", pattern: "%s-removed.txt"},
+			// "exportable" api files are copied to the dist directory instead of the
+			// "everything" api files.
+			{tag: ".exportable.api.txt", pattern: "%s.txt"},
+			{tag: ".exportable.removed-api.txt", pattern: "%s-removed.txt"},
 		} {
 			props.Dists = append(props.Dists, android.Dist{
 				Targets: []string{"sdk", "win_sdk"},
@@ -2018,6 +2024,7 @@
 		Full_api_surface_stub *string
 		System_modules        *string
 		Enable_validation     *bool
+		Stubs_type            *string
 	}{}
 
 	props.Name = proptools.StringPtr(module.apiLibraryModuleName(apiScope))
@@ -2067,6 +2074,7 @@
 
 	props.System_modules = module.deviceProperties.System_modules
 	props.Enable_validation = proptools.BoolPtr(true)
+	props.Stubs_type = proptools.StringPtr("everything")
 
 	mctx.CreateModule(ApiLibraryFactory, &props, module.sdkComponentPropertiesForChildLibrary())
 }
@@ -2709,6 +2717,7 @@
 		Libs                             []string
 		Jars                             []string
 		Compile_dex                      *bool
+		Is_stubs_module                  *bool
 
 		android.UserSuppliedPrebuiltProperties
 	}{}
@@ -2730,6 +2739,7 @@
 		compileDex = proptools.BoolPtr(true)
 	}
 	props.Compile_dex = compileDex
+	props.Is_stubs_module = proptools.BoolPtr(true)
 
 	mctx.CreateModule(ImportFactory, &props, module.sdkComponentPropertiesForChildLibrary())
 }
diff --git a/java/system_modules.go b/java/system_modules.go
index 92e31cd..8e2d5d8 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -64,6 +64,7 @@
 			// useful on Android, and (b) it causes errors with later versions of jlink
 			// when the jdk.internal.module is absent from java.base (as it is here).
 			`  --disable-plugin system-modules && ` +
+			`rm -rf ${workDir} && ` +
 			`cp ${config.JrtFsJar} ${outDir}/lib/`,
 		CommandDeps: []string{
 			"${moduleInfoJavaPath}",
diff --git a/java/testing.go b/java/testing.go
index 04e8c73..631d516 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -523,10 +523,11 @@
 
 	for libName, droidstubs := range extraApiLibraryModules {
 		bp += fmt.Sprintf(`
-            java_api_library {
-                name: "%s",
-                api_contributions: ["%s"],
-            }
+		java_api_library {
+			name: "%s",
+			api_contributions: ["%s"],
+			stubs_type: "everything",
+		}
         `, libName, droidstubs.name+".api.contribution")
 	}
 
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/python/scripts/precompile_python.py b/python/scripts/precompile_python.py
index 80e7c76..aa1a5df 100644
--- a/python/scripts/precompile_python.py
+++ b/python/scripts/precompile_python.py
@@ -24,7 +24,12 @@
 # This file needs to support both python 2 and 3.
 
 
-def process_one_file(info, infile, outzip):
+def process_one_file(name, infile, outzip):
+    # Create a ZipInfo instance with a fixed date to ensure a deterministic output.
+    # Date was chosen to be the same as
+    # https://cs.android.com/android/platform/superproject/main/+/main:build/soong/jar/jar.go;l=36;drc=2863e4535eb65e15f955dc8ed48fa99b1d2a1db5
+    info = zipfile.ZipInfo(filename=name, date_time=(2008, 1, 1, 0, 0, 0))
+
     if not info.filename.endswith('.py'):
         outzip.writestr(info, infile.read())
         return
@@ -37,17 +42,15 @@
     with tempfile.NamedTemporaryFile(prefix="Soong_precompile_", delete=False) as tmp:
         out_name = tmp.name
     try:
-        # Ensure deterministic pyc by using the hash rather than timestamp.
-        # This is required to improve caching in accelerated builds.
-        # Only works on Python 3.7+ (see https://docs.python.org/3/library/py_compile.html#py_compile.PycInvalidationMode)
-        # which should cover most updated branches and developer machines.
+        # Ensure a deterministic .pyc output by using the hash rather than the timestamp.
+        # Only works on Python 3.7+
+        # See https://docs.python.org/3/library/py_compile.html#py_compile.PycInvalidationMode
         if sys.version_info >= (3, 7):
             py_compile.compile(in_name, out_name, info.filename, doraise=True, invalidation_mode=py_compile.PycInvalidationMode.CHECKED_HASH)
         else:
             py_compile.compile(in_name, out_name, info.filename, doraise=True)
         with open(out_name, 'rb') as f:
             info.filename = info.filename + 'c'
-            # Use ZipInfo rather than str to reuse timestamps for deterministic zip files.
             outzip.writestr(info, f.read())
     finally:
         os.remove(in_name)
@@ -62,9 +65,9 @@
 
     with open(args.dst_zip, 'wb') as outf, open(args.src_zip, 'rb') as inf:
         with zipfile.ZipFile(outf, mode='w') as outzip, zipfile.ZipFile(inf, mode='r') as inzip:
-            for info in inzip.infolist():
-                with inzip.open(info.filename, mode='r') as inzipf:
-                    process_one_file(info, inzipf, outzip)
+            for name in inzip.namelist():
+                with inzip.open(name, mode='r') as inzipf:
+                    process_one_file(name, inzipf, outzip)
 
 
 if __name__ == "__main__":
diff --git a/python/test.go b/python/test.go
index 7eb9136..826f353 100644
--- a/python/test.go
+++ b/python/test.go
@@ -158,35 +158,25 @@
 	}
 
 	runner := proptools.StringDefault(p.testProperties.Test_options.Runner, "tradefed")
-	if runner == "tradefed" {
-		p.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
-			TestConfigProp:          p.testProperties.Test_config,
-			TestConfigTemplateProp:  p.testProperties.Test_config_template,
-			TestSuites:              p.binaryProperties.Test_suites,
-			OptionsForAutogenerated: configs,
-			AutoGenConfig:           p.binaryProperties.Auto_gen_config,
-			DeviceTemplate:          "${PythonBinaryHostTestConfigTemplate}",
-			HostTemplate:            "${PythonBinaryHostTestConfigTemplate}",
-		})
-	} else if runner == "mobly" {
-		if p.testProperties.Test_config != nil || p.testProperties.Test_config_template != nil || p.binaryProperties.Auto_gen_config != nil {
-			panic(fmt.Errorf("cannot set test_config, test_config_template or auto_gen_config for mobly test"))
+	template := "${PythonBinaryHostTestConfigTemplate}"
+	if runner == "mobly" {
+		// Add tag to enable Atest mobly runner
+		if !android.InList("mobly", p.testProperties.Test_options.Tags) {
+			p.testProperties.Test_options.Tags = append(p.testProperties.Test_options.Tags, "mobly")
 		}
-
-		for _, testSuite := range p.binaryProperties.Test_suites {
-			if testSuite == "cts" {
-				configs = append(configs, tradefed.Option{Name: "test-suite-tag", Value: "cts"})
-				break
-			}
-		}
-		p.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
-			OptionsForAutogenerated: configs,
-			DeviceTemplate:          "${PythonBinaryHostMoblyTestConfigTemplate}",
-			HostTemplate:            "${PythonBinaryHostMoblyTestConfigTemplate}",
-		})
-	} else {
+		template = "${PythonBinaryHostMoblyTestConfigTemplate}"
+	} else if runner != "tradefed" {
 		panic(fmt.Errorf("unknown python test runner '%s', should be 'tradefed' or 'mobly'", runner))
 	}
+	p.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
+		TestConfigProp:          p.testProperties.Test_config,
+		TestConfigTemplateProp:  p.testProperties.Test_config_template,
+		TestSuites:              p.binaryProperties.Test_suites,
+		OptionsForAutogenerated: configs,
+		AutoGenConfig:           p.binaryProperties.Auto_gen_config,
+		DeviceTemplate:          template,
+		HostTemplate:            template,
+	})
 
 	for _, dataSrcPath := range android.PathsForModuleSrc(ctx, p.testProperties.Data) {
 		p.data = append(p.data, android.DataPath{SrcPath: dataSrcPath})
@@ -228,6 +218,12 @@
 				entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
 			}
 
+			// ATS 2.0 is the test harness for mobly tests and the test config is for ATS 2.0.
+			// Add "v2" suffix to test config name to distinguish it from the config for TF.
+			if proptools.String(p.testProperties.Test_options.Runner) == "mobly" {
+				entries.SetString("LOCAL_TEST_CONFIG_SUFFIX", "v2")
+			}
+
 			entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
 			android.SetAconfigFileMkEntries(&p.ModuleBase, entries, p.mergedAconfigFiles)
 
diff --git a/rust/afdo.go b/rust/afdo.go
index 323ee36..6116c5e 100644
--- a/rust/afdo.go
+++ b/rust/afdo.go
@@ -44,14 +44,14 @@
 		if err != nil {
 			ctx.ModuleErrorf("%s", err.Error())
 		}
-		if fdoProfileName != nil {
+		if fdoProfileName != "" {
 			actx.AddFarVariationDependencies(
 				[]blueprint.Variation{
 					{Mutator: "arch", Variation: actx.Target().ArchVariation()},
 					{Mutator: "os", Variation: "android"},
 				},
 				cc.FdoProfileTag,
-				[]string{*fdoProfileName}...,
+				[]string{fdoProfileName}...,
 			)
 		}
 	}
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 85cc220..454dd87 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -263,10 +263,9 @@
 	// clang: error: '-x c' after last input file has no effect [-Werror,-Wunused-command-line-argument]
 	cflags = append(cflags, "-Wno-unused-command-line-argument")
 
-	// LLVM_NEXT may contain flags that bindgen doesn't recognise. Turn off unknown flags warning.
-	if ctx.Config().IsEnvTrue("LLVM_NEXT") {
-		cflags = append(cflags, "-Wno-unknown-warning-option")
-	}
+	// The Clang version used by CXX can be newer than the one used by Bindgen, and uses warning related flags that
+	// it cannot recognize. Turn off unknown warning flags warning.
+	cflags = append(cflags, "-Wno-unknown-warning-option")
 
 	outputFile := android.PathForModuleOut(ctx, b.BaseSourceProvider.getStem(ctx)+".rs")
 
diff --git a/rust/config/global.go b/rust/config/global.go
index aebbb1b..e28dbaa 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -25,7 +25,7 @@
 	pctx         = android.NewPackageContext("android/soong/rust/config")
 	ExportedVars = android.NewExportedVariables(pctx)
 
-	RustDefaultVersion = "1.74.1"
+	RustDefaultVersion = "1.76.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
diff --git a/rust/config/lints.go b/rust/config/lints.go
index 9322981..735aa16 100644
--- a/rust/config/lints.go
+++ b/rust/config/lints.go
@@ -44,6 +44,7 @@
 	// Default Rust lints that applies to Google-authored modules.
 	defaultRustcLints = []string{
 		"-A deprecated",
+		"-A unknown_lints",
 		"-D missing-docs",
 		"-D warnings",
 		"-D unsafe_op_in_unsafe_fn",
@@ -52,7 +53,10 @@
 	// 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",
 		"-A clippy::unusual-byte-groupings",
 		"-A clippy::upper-case-acronyms",
diff --git a/rust/rust.go b/rust/rust.go
index 245ed2e..668dd8f 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -1063,6 +1063,12 @@
 	return nil
 }
 
+func (d dependencyTag) PropagateAconfigValidation() bool {
+	return d == rlibDepTag || d == sourceDepTag
+}
+
+var _ android.PropagateAconfigValidationDependencyTag = dependencyTag{}
+
 var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}
 
 var (
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 9dda43f..3c08cd8 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -56,6 +56,8 @@
 }
 
 var fuzzerFlags = []string{
+	"-Z external-clangrt=true",
+
 	"-C passes='sancov-module'",
 
 	"--cfg fuzzing",
@@ -73,11 +75,13 @@
 }
 
 var asanFlags = []string{
+	"-Z external-clangrt=true",
 	"-Z sanitizer=address",
 }
 
 // See cc/sanitize.go's hwasanGlobalOptions for global hwasan options.
 var hwasanFlags = []string{
+	"-Z external-clangrt=true",
 	"-Z sanitizer=hwaddress",
 	"-C target-feature=+tagged-globals",
 
@@ -203,11 +207,6 @@
 		s.Address = nil
 	}
 
-	// TODO: Remove once b/304507701 is resolved
-	if Bool(s.Address) && ctx.Host() {
-		s.Address = nil
-	}
-
 	// Memtag_heap is only implemented on AArch64.
 	if ctx.Arch().ArchType != android.Arm64 || !ctx.Os().Bionic() {
 		s.Memtag_heap = nil
diff --git a/rust/testing.go b/rust/testing.go
index 0b34c97..d9cacdc 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -75,6 +75,7 @@
 			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
+			host_supported: true,
 			recovery_available: true,
 			llndk: {
 				symbol_file: "liblog.map.txt",
diff --git a/scripts/Android.bp b/scripts/Android.bp
index e2fd59f..f36329b 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -292,3 +292,16 @@
     name: "keep-flagged-apis",
     src: "keep-flagged-apis.sh",
 }
+
+python_binary_host {
+    name: "merge_directories",
+    main: "merge_directories.py",
+    srcs: [
+        "merge_directories.py",
+    ],
+    version: {
+        py3: {
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/scripts/merge_directories.py b/scripts/merge_directories.py
new file mode 100755
index 0000000..3f8631b
--- /dev/null
+++ b/scripts/merge_directories.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+import argparse
+import os
+import shutil
+import sys
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Given a list of directories, this script will copy the contents of all of "
+        "them into the first directory, erroring out if any duplicate files are found."
+    )
+    parser.add_argument(
+        "--ignore-duplicates",
+        action="store_true",
+        help="Don't error out on duplicate files, just skip them. The file from the earliest "
+        "directory listed on the command line will be the winner."
+    )
+    parser.add_argument(
+        "--file-list",
+        help="Path to a text file containing paths relative to in_dir. Only these paths will be "
+        "copied out of in_dir."
+    )
+    parser.add_argument("out_dir")
+    parser.add_argument("in_dir")
+    args = parser.parse_args()
+
+    if not os.path.isdir(args.out_dir):
+        sys.exit(f"error: {args.out_dir} must be a directory")
+    if not os.path.isdir(args.in_dir):
+        sys.exit(f"error: {args.in_dir} must be a directory")
+
+    file_list = None
+    if args.file_list:
+        with open(file_list_file, "r") as f:
+            file_list = f.read().strip().splitlines()
+
+    in_dir = args.in_dir
+    for root, dirs, files in os.walk(in_dir):
+        rel_root = os.path.relpath(root, in_dir)
+        dst_root = os.path.join(args.out_dir, rel_root)
+        made_parent_dirs = False
+        for f in files:
+            src = os.path.join(root, f)
+            dst = os.path.join(dst_root, f)
+            p = os.path.normpath(os.path.join(rel_root, f))
+            if file_list is not None and p not in file_list:
+                continue
+            if os.path.lexists(dst):
+                if args.ignore_duplicates:
+                    continue
+                sys.exit(f"error: {p} exists in both {args.out_dir} and {in_dir}")
+
+            if not made_parent_dirs:
+                os.makedirs(dst_root, exist_ok=True)
+                made_parent_dirs = True
+
+            shutil.copy2(src, dst, follow_symlinks=False)
+
+if __name__ == "__main__":
+    main()
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/sdk/update.go b/sdk/update.go
index 095e0c2..a731414 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -89,19 +89,6 @@
 	indentLevel int
 }
 
-// generatedFile abstracts operations for writing contents into a file and emit a build rule
-// for the file.
-type generatedFile struct {
-	generatedContents
-	path android.OutputPath
-}
-
-func newGeneratedFile(ctx android.ModuleContext, path ...string) *generatedFile {
-	return &generatedFile{
-		path: android.PathForModuleOut(ctx, path...).OutputPath,
-	}
-}
-
 func (gc *generatedContents) Indent() {
 	gc.indentLevel++
 }
@@ -122,26 +109,6 @@
 	_, _ = fmt.Fprintf(&(gc.content), format, args...)
 }
 
-func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
-	rb := android.NewRuleBuilder(pctx, ctx)
-
-	content := gf.content.String()
-
-	// ninja consumes newline characters in rspfile_content. Prevent it by
-	// escaping the backslash in the newline character. The extra backslash
-	// is removed when the rspfile is written to the actual script file
-	content = strings.ReplaceAll(content, "\n", "\\n")
-
-	rb.Command().
-		Implicits(implicits).
-		Text("echo -n").Text(proptools.ShellEscape(content)).
-		// convert \\n to \n
-		Text("| sed 's/\\\\n/\\n/g' >").Output(gf.path)
-	rb.Command().
-		Text("chmod a+x").Output(gf.path)
-	rb.Build(gf.path.Base(), "Build "+gf.path.Base())
-}
-
 // Collect all the members.
 //
 // Updates the sdk module with a list of sdkMemberVariantDep instances and details as to which
@@ -170,7 +137,7 @@
 
 			var container android.Module
 			if parent != ctx.Module() {
-				container = parent.(android.Module)
+				container = parent
 			}
 
 			minApiLevel := android.MinApiLevelForSdkSnapshot(ctx, child)
@@ -179,7 +146,7 @@
 			s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
 				sdkVariant:             s,
 				memberType:             memberType,
-				variant:                child.(android.Module),
+				variant:                child,
 				minApiLevel:            minApiLevel,
 				container:              container,
 				export:                 export,
@@ -375,7 +342,7 @@
 
 	snapshotDir := android.PathForModuleOut(ctx, "snapshot")
 
-	bp := newGeneratedFile(ctx, "snapshot", "Android.bp")
+	bp := android.PathForModuleOut(ctx, "snapshot", "Android.bp")
 
 	bpFile := &bpFile{
 		modules: make(map[string]*bpModule),
@@ -389,7 +356,7 @@
 		sdk:                   s,
 		snapshotDir:           snapshotDir.OutputPath,
 		copies:                make(map[string]string),
-		filesToZip:            []android.Path{bp.path},
+		filesToZip:            []android.Path{bp},
 		bpFile:                bpFile,
 		prebuiltModules:       make(map[string]*bpModule),
 		allMembersByName:      allMembersByName,
@@ -463,17 +430,14 @@
 	}
 
 	// generate Android.bp
-	bp = newGeneratedFile(ctx, "snapshot", "Android.bp")
-	generateBpContents(&bp.generatedContents, bpFile)
-
-	contents := bp.content.String()
+	contents := generateBpContents(bpFile)
 	// If the snapshot is being generated for the current build release then check the syntax to make
 	// sure that it is compatible.
 	if targetBuildRelease == buildReleaseCurrent {
 		syntaxCheckSnapshotBpFile(ctx, contents)
 	}
 
-	bp.build(pctx, ctx, nil)
+	android.WriteFileRuleVerbatim(ctx, bp, contents)
 
 	// Copy the build number file into the snapshot.
 	builder.CopyToSnapshot(ctx.Config().BuildNumberFile(ctx), BUILD_NUMBER_FILE)
@@ -522,16 +486,14 @@
 	modules := s.generateInfoData(ctx, memberVariantDeps)
 
 	// Output the modules information as pretty printed JSON.
-	info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
+	info := android.PathForModuleOut(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
 	output, err := json.MarshalIndent(modules, "", "  ")
 	if err != nil {
 		ctx.ModuleErrorf("error generating %q: %s", info, err)
 	}
 	builder.infoContents = string(output)
-	info.generatedContents.UnindentedPrintf("%s", output)
-	info.build(pctx, ctx, nil)
-	infoPath := info.path
-	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath)
+	android.WriteFileRuleVerbatim(ctx, info, builder.infoContents)
+	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), info.Base(), info)
 	s.infoFile = android.OptionalPathForPath(installedInfo)
 
 	// Install the zip, making sure that the info file has been installed as well.
@@ -718,105 +680,6 @@
 	}
 }
 
-// snapshotModuleStaticProperties contains snapshot static (i.e. not dynamically generated) properties.
-type snapshotModuleStaticProperties struct {
-	Compile_multilib string `android:"arch_variant"`
-}
-
-// combinedSnapshotModuleProperties are the properties that are associated with the snapshot module.
-type combinedSnapshotModuleProperties struct {
-	// The sdk variant from which this information was collected.
-	sdkVariant *sdk
-
-	// Static snapshot module properties.
-	staticProperties *snapshotModuleStaticProperties
-
-	// The dynamically generated member list properties.
-	dynamicProperties interface{}
-}
-
-// collateSnapshotModuleInfo collates all the snapshot module info from supplied sdk variants.
-func (s *sdk) collateSnapshotModuleInfo(ctx android.BaseModuleContext, sdkVariants []*sdk, memberVariantDeps []sdkMemberVariantDep) []*combinedSnapshotModuleProperties {
-	sdkVariantToCombinedProperties := map[*sdk]*combinedSnapshotModuleProperties{}
-	var list []*combinedSnapshotModuleProperties
-	for _, sdkVariant := range sdkVariants {
-		staticProperties := &snapshotModuleStaticProperties{
-			Compile_multilib: sdkVariant.multilibUsages.String(),
-		}
-		dynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
-
-		combinedProperties := &combinedSnapshotModuleProperties{
-			sdkVariant:        sdkVariant,
-			staticProperties:  staticProperties,
-			dynamicProperties: dynamicProperties,
-		}
-		sdkVariantToCombinedProperties[sdkVariant] = combinedProperties
-
-		list = append(list, combinedProperties)
-	}
-
-	for _, memberVariantDep := range memberVariantDeps {
-		// If the member dependency is internal then do not add the dependency to the snapshot member
-		// list properties.
-		if !memberVariantDep.export {
-			continue
-		}
-
-		combined := sdkVariantToCombinedProperties[memberVariantDep.sdkVariant]
-		memberListProperty := s.memberTypeListProperty(memberVariantDep.memberType)
-		memberName := ctx.OtherModuleName(memberVariantDep.variant)
-
-		if memberListProperty.getter == nil {
-			continue
-		}
-
-		// Append the member to the appropriate list, if it is not already present in the list.
-		memberList := memberListProperty.getter(combined.dynamicProperties)
-		if !android.InList(memberName, memberList) {
-			memberList = append(memberList, memberName)
-		}
-		memberListProperty.setter(combined.dynamicProperties, memberList)
-	}
-
-	return list
-}
-
-func (s *sdk) optimizeSnapshotModuleProperties(ctx android.ModuleContext, list []*combinedSnapshotModuleProperties) *combinedSnapshotModuleProperties {
-
-	// Extract the dynamic properties and add them to a list of propertiesContainer.
-	propertyContainers := []propertiesContainer{}
-	for _, i := range list {
-		propertyContainers = append(propertyContainers, sdkVariantPropertiesContainer{
-			sdkVariant: i.sdkVariant,
-			properties: i.dynamicProperties,
-		})
-	}
-
-	// Extract the common members, removing them from the original properties.
-	commonDynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
-	extractor := newCommonValueExtractor(commonDynamicProperties)
-	extractCommonProperties(ctx, extractor, commonDynamicProperties, propertyContainers)
-
-	// Extract the static properties and add them to a list of propertiesContainer.
-	propertyContainers = []propertiesContainer{}
-	for _, i := range list {
-		propertyContainers = append(propertyContainers, sdkVariantPropertiesContainer{
-			sdkVariant: i.sdkVariant,
-			properties: i.staticProperties,
-		})
-	}
-
-	commonStaticProperties := &snapshotModuleStaticProperties{}
-	extractor = newCommonValueExtractor(commonStaticProperties)
-	extractCommonProperties(ctx, extractor, &commonStaticProperties, propertyContainers)
-
-	return &combinedSnapshotModuleProperties{
-		sdkVariant:        nil,
-		staticProperties:  commonStaticProperties,
-		dynamicProperties: commonDynamicProperties,
-	}
-}
-
 type propertyTag struct {
 	name string
 }
@@ -885,7 +748,8 @@
 	}
 }
 
-func generateBpContents(contents *generatedContents, bpFile *bpFile) {
+func generateBpContents(bpFile *bpFile) string {
+	contents := &generatedContents{}
 	contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n")
 	for _, bpModule := range bpFile.order {
 		contents.IndentedPrintf("\n")
@@ -893,6 +757,7 @@
 		outputPropertySet(contents, bpModule.bpPropertySet)
 		contents.IndentedPrintf("}\n")
 	}
+	return contents.content.String()
 }
 
 func outputPropertySet(contents *generatedContents, set *bpPropertySet) {
@@ -1007,7 +872,7 @@
 		contents.IndentedPrintf("}")
 
 	default:
-		panic(fmt.Errorf("Unknown type: %T of value %#v", value, value))
+		panic(fmt.Errorf("unknown type: %T of value %#v", value, value))
 	}
 }
 
@@ -1018,9 +883,7 @@
 }
 
 func (s *sdk) GetAndroidBpContentsForTests() string {
-	contents := &generatedContents{}
-	generateBpContents(contents, s.builderForTests.bpFile)
-	return contents.content.String()
+	return generateBpContents(s.builderForTests.bpFile)
 }
 
 func (s *sdk) GetInfoContentsForTests() string {
@@ -1334,7 +1197,7 @@
 	case "lib64":
 		return m | multilib64
 	default:
-		panic(fmt.Errorf("Unknown Multilib field in ArchType, expected 'lib32' or 'lib64', found %q", multilib))
+		panic(fmt.Errorf("unknown Multilib field in ArchType, expected 'lib32' or 'lib64', found %q", multilib))
 	}
 }
 
@@ -1349,7 +1212,7 @@
 	case multilibBoth:
 		return "both"
 	default:
-		panic(fmt.Errorf("Unknown multilib value, found %b, expected one of %b, %b, %b or %b",
+		panic(fmt.Errorf("unknown multilib value, found %b, expected one of %b, %b, %b or %b",
 			m, multilibNone, multilib32, multilib64, multilibBoth))
 	}
 }
@@ -2302,20 +2165,6 @@
 	optimizableProperties() interface{}
 }
 
-// A wrapper for sdk variant related properties to allow them to be optimized.
-type sdkVariantPropertiesContainer struct {
-	sdkVariant *sdk
-	properties interface{}
-}
-
-func (c sdkVariantPropertiesContainer) optimizableProperties() interface{} {
-	return c.properties
-}
-
-func (c sdkVariantPropertiesContainer) String() string {
-	return c.sdkVariant.String()
-}
-
 // Extract common properties from a slice of property structures of the same type.
 //
 // All the property structures must be of the same type.
diff --git a/starlark_import/Android.bp b/starlark_import/Android.bp
deleted file mode 100644
index b43217b..0000000
--- a/starlark_import/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-starlark",
-    pkgPath: "android/soong/starlark_import",
-    srcs: [
-        "starlark_import.go",
-        "unmarshal.go",
-    ],
-    testSrcs: [
-        "starlark_import_test.go",
-        "unmarshal_test.go",
-    ],
-    deps: [
-        "go-starlark-starlark",
-        "go-starlark-starlarkstruct",
-        "go-starlark-starlarkjson",
-        "go-starlark-starlarktest",
-    ],
-}
diff --git a/starlark_import/README.md b/starlark_import/README.md
deleted file mode 100644
index e444759..0000000
--- a/starlark_import/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# starlark_import package
-
-This allows soong to read constant information from starlark files. At package initialization
-time, soong will read `build/bazel/constants_exported_to_soong.bzl`, and then make the
-variables from that file available via `starlark_import.GetStarlarkValue()`. So to import
-a new variable, it must be added to `constants_exported_to_soong.bzl` and then it can
-be accessed by name.
-
-Only constant information can be read, since this is not a full bazel execution but a
-standalone starlark interpreter. This means you can't use bazel contructs like `rule`,
-`provider`, `select`, `glob`, etc.
-
-All starlark files that were loaded must be added as ninja deps that cause soong to rerun.
-The loaded files can be retrieved via `starlark_import.GetNinjaDeps()`.
diff --git a/starlark_import/starlark_import.go b/starlark_import/starlark_import.go
deleted file mode 100644
index ebe4247..0000000
--- a/starlark_import/starlark_import.go
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright 2023 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 starlark_import
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"sync"
-	"time"
-
-	"go.starlark.net/starlark"
-	"go.starlark.net/starlarkjson"
-	"go.starlark.net/starlarkstruct"
-)
-
-func init() {
-	go func() {
-		startTime := time.Now()
-		v, d, err := runStarlarkFile("//build/bazel/constants_exported_to_soong.bzl")
-		endTime := time.Now()
-		//fmt.Fprintf(os.Stderr, "starlark run time: %s\n", endTime.Sub(startTime).String())
-		globalResult.Set(starlarkResult{
-			values:    v,
-			ninjaDeps: d,
-			err:       err,
-			startTime: startTime,
-			endTime:   endTime,
-		})
-	}()
-}
-
-type starlarkResult struct {
-	values    starlark.StringDict
-	ninjaDeps []string
-	err       error
-	startTime time.Time
-	endTime   time.Time
-}
-
-// setOnce wraps a value and exposes Set() and Get() accessors for it.
-// The Get() calls will block until a Set() has been called.
-// A second call to Set() will panic.
-// setOnce must be created using newSetOnce()
-type setOnce[T any] struct {
-	value T
-	lock  sync.Mutex
-	wg    sync.WaitGroup
-	isSet bool
-}
-
-func (o *setOnce[T]) Set(value T) {
-	o.lock.Lock()
-	defer o.lock.Unlock()
-	if o.isSet {
-		panic("Value already set")
-	}
-
-	o.value = value
-	o.isSet = true
-	o.wg.Done()
-}
-
-func (o *setOnce[T]) Get() T {
-	if !o.isSet {
-		o.wg.Wait()
-	}
-	return o.value
-}
-
-func newSetOnce[T any]() *setOnce[T] {
-	result := &setOnce[T]{}
-	result.wg.Add(1)
-	return result
-}
-
-var globalResult = newSetOnce[starlarkResult]()
-
-func GetStarlarkValue[T any](key string) (T, error) {
-	result := globalResult.Get()
-	if result.err != nil {
-		var zero T
-		return zero, result.err
-	}
-	if !result.values.Has(key) {
-		var zero T
-		return zero, fmt.Errorf("a starlark variable by that name wasn't found, did you update //build/bazel/constants_exported_to_soong.bzl?")
-	}
-	return Unmarshal[T](result.values[key])
-}
-
-func GetNinjaDeps() ([]string, error) {
-	result := globalResult.Get()
-	if result.err != nil {
-		return nil, result.err
-	}
-	return result.ninjaDeps, nil
-}
-
-func getTopDir() (string, error) {
-	// It's hard to communicate the top dir to this package in any other way than reading the
-	// arguments directly, because we need to know this at package initialization time. Many
-	// soong constants that we'd like to read from starlark are initialized during package
-	// initialization.
-	for i, arg := range os.Args {
-		if arg == "--top" {
-			if i < len(os.Args)-1 && os.Args[i+1] != "" {
-				return os.Args[i+1], nil
-			}
-		}
-	}
-
-	// When running tests, --top is not passed. Instead, search for the top dir manually
-	cwd, err := os.Getwd()
-	if err != nil {
-		return "", err
-	}
-	for cwd != "/" {
-		if _, err := os.Stat(filepath.Join(cwd, "build/soong/soong_ui.bash")); err == nil {
-			return cwd, nil
-		}
-		cwd = filepath.Dir(cwd)
-	}
-	return "", fmt.Errorf("could not find top dir")
-}
-
-const callerDirKey = "callerDir"
-
-type modentry struct {
-	globals starlark.StringDict
-	err     error
-}
-
-func unsupportedMethod(t *starlark.Thread, fn *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
-	return nil, fmt.Errorf("%sthis file is read by soong, and must therefore be pure starlark and include only constant information. %q is not allowed", t.CallStack().String(), fn.Name())
-}
-
-var builtins = starlark.StringDict{
-	"aspect":     starlark.NewBuiltin("aspect", unsupportedMethod),
-	"glob":       starlark.NewBuiltin("glob", unsupportedMethod),
-	"json":       starlarkjson.Module,
-	"provider":   starlark.NewBuiltin("provider", unsupportedMethod),
-	"rule":       starlark.NewBuiltin("rule", unsupportedMethod),
-	"struct":     starlark.NewBuiltin("struct", starlarkstruct.Make),
-	"select":     starlark.NewBuiltin("select", unsupportedMethod),
-	"transition": starlark.NewBuiltin("transition", unsupportedMethod),
-}
-
-// Takes a module name (the first argument to the load() function) and returns the path
-// it's trying to load, stripping out leading //, and handling leading :s.
-func cleanModuleName(moduleName string, callerDir string) (string, error) {
-	if strings.Count(moduleName, ":") > 1 {
-		return "", fmt.Errorf("at most 1 colon must be present in starlark path: %s", moduleName)
-	}
-
-	// We don't have full support for external repositories, but at least support skylib's dicts.
-	if moduleName == "@bazel_skylib//lib:dicts.bzl" {
-		return "external/bazel-skylib/lib/dicts.bzl", nil
-	}
-
-	localLoad := false
-	if strings.HasPrefix(moduleName, "@//") {
-		moduleName = moduleName[3:]
-	} else if strings.HasPrefix(moduleName, "//") {
-		moduleName = moduleName[2:]
-	} else if strings.HasPrefix(moduleName, ":") {
-		moduleName = moduleName[1:]
-		localLoad = true
-	} else {
-		return "", fmt.Errorf("load path must start with // or :")
-	}
-
-	if ix := strings.LastIndex(moduleName, ":"); ix >= 0 {
-		moduleName = moduleName[:ix] + string(os.PathSeparator) + moduleName[ix+1:]
-	}
-
-	if filepath.Clean(moduleName) != moduleName {
-		return "", fmt.Errorf("load path must be clean, found: %s, expected: %s", moduleName, filepath.Clean(moduleName))
-	}
-	if strings.HasPrefix(moduleName, "../") {
-		return "", fmt.Errorf("load path must not start with ../: %s", moduleName)
-	}
-	if strings.HasPrefix(moduleName, "/") {
-		return "", fmt.Errorf("load path starts with /, use // for a absolute path: %s", moduleName)
-	}
-
-	if localLoad {
-		return filepath.Join(callerDir, moduleName), nil
-	}
-
-	return moduleName, nil
-}
-
-// loader implements load statement. The format of the loaded module URI is
-//
-//	[//path]:base
-//
-// The file path is $ROOT/path/base if path is present, <caller_dir>/base otherwise.
-func loader(thread *starlark.Thread, module string, topDir string, moduleCache map[string]*modentry, moduleCacheLock *sync.Mutex, filesystem map[string]string) (starlark.StringDict, error) {
-	modulePath, err := cleanModuleName(module, thread.Local(callerDirKey).(string))
-	if err != nil {
-		return nil, err
-	}
-	moduleCacheLock.Lock()
-	e, ok := moduleCache[modulePath]
-	if e == nil {
-		if ok {
-			moduleCacheLock.Unlock()
-			return nil, fmt.Errorf("cycle in load graph")
-		}
-
-		// Add a placeholder to indicate "load in progress".
-		moduleCache[modulePath] = nil
-		moduleCacheLock.Unlock()
-
-		childThread := &starlark.Thread{Name: "exec " + module, Load: thread.Load}
-
-		// Cheating for the sake of testing:
-		// propagate starlarktest's Reporter key, otherwise testing
-		// the load function may cause panic in starlarktest code.
-		const testReporterKey = "Reporter"
-		if v := thread.Local(testReporterKey); v != nil {
-			childThread.SetLocal(testReporterKey, v)
-		}
-
-		childThread.SetLocal(callerDirKey, filepath.Dir(modulePath))
-
-		if filesystem != nil {
-			globals, err := starlark.ExecFile(childThread, filepath.Join(topDir, modulePath), filesystem[modulePath], builtins)
-			e = &modentry{globals, err}
-		} else {
-			globals, err := starlark.ExecFile(childThread, filepath.Join(topDir, modulePath), nil, builtins)
-			e = &modentry{globals, err}
-		}
-
-		// Update the cache.
-		moduleCacheLock.Lock()
-		moduleCache[modulePath] = e
-	}
-	moduleCacheLock.Unlock()
-	return e.globals, e.err
-}
-
-// Run runs the given starlark file and returns its global variables and a list of all starlark
-// files that were loaded. The top dir for starlark's // is found via getTopDir().
-func runStarlarkFile(filename string) (starlark.StringDict, []string, error) {
-	topDir, err := getTopDir()
-	if err != nil {
-		return nil, nil, err
-	}
-	return runStarlarkFileWithFilesystem(filename, topDir, nil)
-}
-
-func runStarlarkFileWithFilesystem(filename string, topDir string, filesystem map[string]string) (starlark.StringDict, []string, error) {
-	if !strings.HasPrefix(filename, "//") && !strings.HasPrefix(filename, ":") {
-		filename = "//" + filename
-	}
-	filename, err := cleanModuleName(filename, "")
-	if err != nil {
-		return nil, nil, err
-	}
-	moduleCache := make(map[string]*modentry)
-	moduleCache[filename] = nil
-	moduleCacheLock := &sync.Mutex{}
-	mainThread := &starlark.Thread{
-		Name: "main",
-		Print: func(_ *starlark.Thread, msg string) {
-			// Ignore prints
-		},
-		Load: func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
-			return loader(thread, module, topDir, moduleCache, moduleCacheLock, filesystem)
-		},
-	}
-	mainThread.SetLocal(callerDirKey, filepath.Dir(filename))
-
-	var result starlark.StringDict
-	if filesystem != nil {
-		result, err = starlark.ExecFile(mainThread, filepath.Join(topDir, filename), filesystem[filename], builtins)
-	} else {
-		result, err = starlark.ExecFile(mainThread, filepath.Join(topDir, filename), nil, builtins)
-	}
-	return result, sortedStringKeys(moduleCache), err
-}
-
-func sortedStringKeys(m map[string]*modentry) []string {
-	s := make([]string, 0, len(m))
-	for k := range m {
-		s = append(s, k)
-	}
-	sort.Strings(s)
-	return s
-}
diff --git a/starlark_import/starlark_import_test.go b/starlark_import/starlark_import_test.go
deleted file mode 100644
index 8a58e3b..0000000
--- a/starlark_import/starlark_import_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2023 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 starlark_import
-
-import (
-	"strings"
-	"testing"
-
-	"go.starlark.net/starlark"
-)
-
-func TestBasic(t *testing.T) {
-	globals, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-my_string = "hello, world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-}
-
-func TestLoad(t *testing.T) {
-	globals, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load("//b.bzl", _b_string = "my_string")
-my_string = "hello, " + _b_string
-`,
-		"b.bzl": `
-my_string = "world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-}
-
-func TestLoadRelative(t *testing.T) {
-	globals, ninjaDeps, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load(":b.bzl", _b_string = "my_string")
-load("//foo/c.bzl", _c_string = "my_string")
-my_string = "hello, " + _b_string
-c_string = _c_string
-`,
-		"b.bzl": `
-my_string = "world!"
-`,
-		"foo/c.bzl": `
-load(":d.bzl", _d_string = "my_string")
-my_string = "hello, " + _d_string
-`,
-		"foo/d.bzl": `
-my_string = "world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-
-	expectedNinjaDeps := []string{
-		"a.bzl",
-		"b.bzl",
-		"foo/c.bzl",
-		"foo/d.bzl",
-	}
-	if !slicesEqual(ninjaDeps, expectedNinjaDeps) {
-		t.Errorf("Expected %v ninja deps, got %v", expectedNinjaDeps, ninjaDeps)
-	}
-}
-
-func TestLoadCycle(t *testing.T) {
-	_, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load(":b.bzl", _b_string = "my_string")
-my_string = "hello, " + _b_string
-`,
-		"b.bzl": `
-load(":a.bzl", _a_string = "my_string")
-my_string = "hello, " + _a_string
-`})
-	if err == nil || !strings.Contains(err.Error(), "cycle in load graph") {
-		t.Errorf("Expected cycle in load graph, got: %v", err)
-		return
-	}
-}
-
-func slicesEqual[T comparable](a []T, b []T) bool {
-	if len(a) != len(b) {
-		return false
-	}
-	for i := range a {
-		if a[i] != b[i] {
-			return false
-		}
-	}
-	return true
-}
diff --git a/starlark_import/unmarshal.go b/starlark_import/unmarshal.go
deleted file mode 100644
index b243471..0000000
--- a/starlark_import/unmarshal.go
+++ /dev/null
@@ -1,304 +0,0 @@
-// Copyright 2023 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 starlark_import
-
-import (
-	"fmt"
-	"math"
-	"reflect"
-	"unsafe"
-
-	"go.starlark.net/starlark"
-	"go.starlark.net/starlarkstruct"
-)
-
-func Unmarshal[T any](value starlark.Value) (T, error) {
-	x, err := UnmarshalReflect(value, reflect.TypeOf((*T)(nil)).Elem())
-	return x.Interface().(T), err
-}
-
-func UnmarshalReflect(value starlark.Value, ty reflect.Type) (reflect.Value, error) {
-	if ty == reflect.TypeOf((*starlark.Value)(nil)).Elem() {
-		return reflect.ValueOf(value), nil
-	}
-	zero := reflect.Zero(ty)
-	if value == nil {
-		panic("nil value")
-	}
-	var result reflect.Value
-	if ty.Kind() == reflect.Interface {
-		var err error
-		ty, err = typeOfStarlarkValue(value)
-		if err != nil {
-			return zero, err
-		}
-	}
-	if ty.Kind() == reflect.Map {
-		result = reflect.MakeMap(ty)
-	} else {
-		result = reflect.Indirect(reflect.New(ty))
-	}
-
-	switch v := value.(type) {
-	case starlark.String:
-		if result.Type().Kind() != reflect.String {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		result.SetString(v.GoString())
-	case starlark.Int:
-		signedValue, signedOk := v.Int64()
-		unsignedValue, unsignedOk := v.Uint64()
-		switch result.Type().Kind() {
-		case reflect.Int64:
-			if !signedOk {
-				return zero, fmt.Errorf("starlark int didn't fit in go int64")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int32:
-			if !signedOk || signedValue > math.MaxInt32 || signedValue < math.MinInt32 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int32")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int16:
-			if !signedOk || signedValue > math.MaxInt16 || signedValue < math.MinInt16 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int16")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int8:
-			if !signedOk || signedValue > math.MaxInt8 || signedValue < math.MinInt8 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int8")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int:
-			if !signedOk || signedValue > math.MaxInt || signedValue < math.MinInt {
-				return zero, fmt.Errorf("starlark int didn't fit in go int")
-			}
-			result.SetInt(signedValue)
-		case reflect.Uint64:
-			if !unsignedOk {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint64")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint32:
-			if !unsignedOk || unsignedValue > math.MaxUint32 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint32")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint16:
-			if !unsignedOk || unsignedValue > math.MaxUint16 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint16")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint8:
-			if !unsignedOk || unsignedValue > math.MaxUint8 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint8")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint:
-			if !unsignedOk || unsignedValue > math.MaxUint {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint")
-			}
-			result.SetUint(unsignedValue)
-		default:
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-	case starlark.Float:
-		f := float64(v)
-		switch result.Type().Kind() {
-		case reflect.Float64:
-			result.SetFloat(f)
-		case reflect.Float32:
-			if f > math.MaxFloat32 || f < -math.MaxFloat32 {
-				return zero, fmt.Errorf("starlark float didn't fit in go float32")
-			}
-			result.SetFloat(f)
-		default:
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-	case starlark.Bool:
-		if result.Type().Kind() != reflect.Bool {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		result.SetBool(bool(v))
-	case starlark.Tuple:
-		if result.Type().Kind() != reflect.Slice {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		elemType := result.Type().Elem()
-		// TODO: Add this grow call when we're on go 1.20
-		//result.Grow(v.Len())
-		for i := 0; i < v.Len(); i++ {
-			elem, err := UnmarshalReflect(v.Index(i), elemType)
-			if err != nil {
-				return zero, err
-			}
-			result = reflect.Append(result, elem)
-		}
-	case *starlark.List:
-		if result.Type().Kind() != reflect.Slice {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		elemType := result.Type().Elem()
-		// TODO: Add this grow call when we're on go 1.20
-		//result.Grow(v.Len())
-		for i := 0; i < v.Len(); i++ {
-			elem, err := UnmarshalReflect(v.Index(i), elemType)
-			if err != nil {
-				return zero, err
-			}
-			result = reflect.Append(result, elem)
-		}
-	case *starlark.Dict:
-		if result.Type().Kind() != reflect.Map {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		keyType := result.Type().Key()
-		valueType := result.Type().Elem()
-		for _, pair := range v.Items() {
-			key := pair.Index(0)
-			value := pair.Index(1)
-
-			unmarshalledKey, err := UnmarshalReflect(key, keyType)
-			if err != nil {
-				return zero, err
-			}
-			unmarshalledValue, err := UnmarshalReflect(value, valueType)
-			if err != nil {
-				return zero, err
-			}
-
-			result.SetMapIndex(unmarshalledKey, unmarshalledValue)
-		}
-	case *starlarkstruct.Struct:
-		if result.Type().Kind() != reflect.Struct {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		if result.NumField() != len(v.AttrNames()) {
-			return zero, fmt.Errorf("starlark struct and go struct have different number of fields (%d and %d)", len(v.AttrNames()), result.NumField())
-		}
-		for _, attrName := range v.AttrNames() {
-			attr, err := v.Attr(attrName)
-			if err != nil {
-				return zero, err
-			}
-
-			// TODO(b/279787235): this should probably support tags to rename the field
-			resultField := result.FieldByName(attrName)
-			if resultField == (reflect.Value{}) {
-				return zero, fmt.Errorf("starlark struct had field %s, but requested struct type did not", attrName)
-			}
-			// This hack allows us to change unexported fields
-			resultField = reflect.NewAt(resultField.Type(), unsafe.Pointer(resultField.UnsafeAddr())).Elem()
-			x, err := UnmarshalReflect(attr, resultField.Type())
-			if err != nil {
-				return zero, err
-			}
-			resultField.Set(x)
-		}
-	default:
-		return zero, fmt.Errorf("unimplemented starlark type: %s", value.Type())
-	}
-
-	return result, nil
-}
-
-func typeOfStarlarkValue(value starlark.Value) (reflect.Type, error) {
-	var err error
-	switch v := value.(type) {
-	case starlark.String:
-		return reflect.TypeOf(""), nil
-	case *starlark.List:
-		innerType := reflect.TypeOf("")
-		if v.Len() > 0 {
-			innerType, err = typeOfStarlarkValue(v.Index(0))
-			if err != nil {
-				return nil, err
-			}
-		}
-		for i := 1; i < v.Len(); i++ {
-			innerTypeI, err := typeOfStarlarkValue(v.Index(i))
-			if err != nil {
-				return nil, err
-			}
-			if innerType != innerTypeI {
-				return nil, fmt.Errorf("List must contain elements of entirely the same type, found %v and %v", innerType, innerTypeI)
-			}
-		}
-		return reflect.SliceOf(innerType), nil
-	case *starlark.Dict:
-		keyType := reflect.TypeOf("")
-		valueType := reflect.TypeOf("")
-		keys := v.Keys()
-		if v.Len() > 0 {
-			firstKey := keys[0]
-			keyType, err = typeOfStarlarkValue(firstKey)
-			if err != nil {
-				return nil, err
-			}
-			firstValue, found, err := v.Get(firstKey)
-			if !found {
-				err = fmt.Errorf("value not found")
-			}
-			if err != nil {
-				return nil, err
-			}
-			valueType, err = typeOfStarlarkValue(firstValue)
-			if err != nil {
-				return nil, err
-			}
-		}
-		for _, key := range keys {
-			keyTypeI, err := typeOfStarlarkValue(key)
-			if err != nil {
-				return nil, err
-			}
-			if keyType != keyTypeI {
-				return nil, fmt.Errorf("dict must contain elements of entirely the same type, found %v and %v", keyType, keyTypeI)
-			}
-			value, found, err := v.Get(key)
-			if !found {
-				err = fmt.Errorf("value not found")
-			}
-			if err != nil {
-				return nil, err
-			}
-			valueTypeI, err := typeOfStarlarkValue(value)
-			if valueType.Kind() != reflect.Interface && valueTypeI != valueType {
-				// If we see conflicting value types, change the result value type to an empty interface
-				valueType = reflect.TypeOf([]interface{}{}).Elem()
-			}
-		}
-		return reflect.MapOf(keyType, valueType), nil
-	case starlark.Int:
-		return reflect.TypeOf(0), nil
-	case starlark.Float:
-		return reflect.TypeOf(0.0), nil
-	case starlark.Bool:
-		return reflect.TypeOf(true), nil
-	default:
-		return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
-	}
-}
-
-// UnmarshalNoneable is like Unmarshal, but it will accept None as the top level (but not nested)
-// starlark value. If the value is None, a nil pointer will be returned, otherwise a pointer
-// to the result of Unmarshal will be returned.
-func UnmarshalNoneable[T any](value starlark.Value) (*T, error) {
-	if _, ok := value.(starlark.NoneType); ok {
-		return nil, nil
-	}
-	ret, err := Unmarshal[T](value)
-	return &ret, err
-}
diff --git a/starlark_import/unmarshal_test.go b/starlark_import/unmarshal_test.go
deleted file mode 100644
index bc0ea4c..0000000
--- a/starlark_import/unmarshal_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2023 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 starlark_import
-
-import (
-	"reflect"
-	"testing"
-
-	"go.starlark.net/starlark"
-)
-
-func createStarlarkValue(t *testing.T, code string) starlark.Value {
-	t.Helper()
-	result, err := starlark.ExecFile(&starlark.Thread{}, "main.bzl", "x = "+code, builtins)
-	if err != nil {
-		panic(err)
-	}
-	return result["x"]
-}
-
-func TestUnmarshalConcreteType(t *testing.T) {
-	x, err := Unmarshal[string](createStarlarkValue(t, `"foo"`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	if x != "foo" {
-		t.Errorf(`Expected "foo", got %q`, x)
-	}
-}
-
-func TestUnmarshalConcreteTypeWithInterfaces(t *testing.T) {
-	x, err := Unmarshal[map[string]map[string]interface{}](createStarlarkValue(t,
-		`{"foo": {"foo2": "foo3"}, "bar": {"bar2": ["bar3"]}}`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	expected := map[string]map[string]interface{}{
-		"foo": {"foo2": "foo3"},
-		"bar": {"bar2": []string{"bar3"}},
-	}
-	if !reflect.DeepEqual(x, expected) {
-		t.Errorf(`Expected %v, got %v`, expected, x)
-	}
-}
-
-func TestUnmarshalToStarlarkValue(t *testing.T) {
-	x, err := Unmarshal[map[string]starlark.Value](createStarlarkValue(t,
-		`{"foo": "Hi", "bar": None}`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	if x["foo"].(starlark.String).GoString() != "Hi" {
-		t.Errorf("Expected \"Hi\", got: %q", x["foo"].(starlark.String).GoString())
-	}
-	if x["bar"].Type() != "NoneType" {
-		t.Errorf("Expected \"NoneType\", got: %q", x["bar"].Type())
-	}
-}
-
-func TestUnmarshal(t *testing.T) {
-	testCases := []struct {
-		input    string
-		expected interface{}
-	}{
-		{
-			input:    `"foo"`,
-			expected: "foo",
-		},
-		{
-			input:    `5`,
-			expected: 5,
-		},
-		{
-			input:    `["foo", "bar"]`,
-			expected: []string{"foo", "bar"},
-		},
-		{
-			input:    `("foo", "bar")`,
-			expected: []string{"foo", "bar"},
-		},
-		{
-			input:    `("foo",5)`,
-			expected: []interface{}{"foo", 5},
-		},
-		{
-			input:    `{"foo": 5, "bar": 10}`,
-			expected: map[string]int{"foo": 5, "bar": 10},
-		},
-		{
-			input:    `{"foo": ["qux"], "bar": []}`,
-			expected: map[string][]string{"foo": {"qux"}, "bar": nil},
-		},
-		{
-			input: `struct(Foo="foo", Bar=5)`,
-			expected: struct {
-				Foo string
-				Bar int
-			}{Foo: "foo", Bar: 5},
-		},
-		{
-			// Unexported fields version of the above
-			input: `struct(foo="foo", bar=5)`,
-			expected: struct {
-				foo string
-				bar int
-			}{foo: "foo", bar: 5},
-		},
-		{
-			input: `{"foo": "foo2", "bar": ["bar2"], "baz": 5, "qux": {"qux2": "qux3"}, "quux": {"quux2": "quux3", "quux4": 5}}`,
-			expected: map[string]interface{}{
-				"foo": "foo2",
-				"bar": []string{"bar2"},
-				"baz": 5,
-				"qux": map[string]string{"qux2": "qux3"},
-				"quux": map[string]interface{}{
-					"quux2": "quux3",
-					"quux4": 5,
-				},
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		x, err := UnmarshalReflect(createStarlarkValue(t, tc.input), reflect.TypeOf(tc.expected))
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		if !reflect.DeepEqual(x.Interface(), tc.expected) {
-			t.Errorf(`Expected %#v, got %#v`, tc.expected, x.Interface())
-		}
-	}
-}
diff --git a/sysprop/Android.bp b/sysprop/Android.bp
index 1d5eb31..a00a5e4 100644
--- a/sysprop/Android.bp
+++ b/sysprop/Android.bp
@@ -11,6 +11,7 @@
         "soong-android",
         "soong-cc",
         "soong-java",
+        "soong-rust",
     ],
     srcs: [
         "sysprop_library.go",
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 766f3e7..b9b68be 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -21,6 +21,7 @@
 	"io"
 	"os"
 	"path"
+	"strings"
 	"sync"
 
 	"github.com/google/blueprint"
@@ -29,6 +30,7 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/java"
+	"android/soong/rust"
 )
 
 type dependencyTag struct {
@@ -51,7 +53,14 @@
 	genSrcjars android.Paths
 }
 
+type syspropRustGenRule struct {
+	*rust.BaseSourceProvider
+
+	properties rustLibraryProperties
+}
+
 var _ android.OutputFileProducer = (*syspropJavaGenRule)(nil)
+var _ rust.SourceProvider = (*syspropRustGenRule)(nil)
 
 var (
 	syspropJava = pctx.AndroidStaticRule("syspropJava",
@@ -64,11 +73,20 @@
 				"$soongZipCmd",
 			},
 		}, "scope")
+	syspropRust = pctx.AndroidStaticRule("syspropRust",
+		blueprint.RuleParams{
+			Command: `rm -rf $out_dir && mkdir -p $out_dir && ` +
+				`$syspropRustCmd --scope $scope --rust-output-dir $out_dir $in`,
+			CommandDeps: []string{
+				"$syspropRustCmd",
+			},
+		}, "scope", "out_dir")
 )
 
 func init() {
 	pctx.HostBinToolVariable("soongZipCmd", "soong_zip")
 	pctx.HostBinToolVariable("syspropJavaCmd", "sysprop_java")
+	pctx.HostBinToolVariable("syspropRustCmd", "sysprop_rust")
 }
 
 // syspropJavaGenRule module generates srcjar containing generated java APIs.
@@ -122,6 +140,75 @@
 	return g
 }
 
+// syspropRustGenRule module generates rust source files containing generated rust APIs.
+// It also depends on check api rule, so api check has to pass to use sysprop_library.
+func (g *syspropRustGenRule) GenerateSource(ctx rust.ModuleContext, deps rust.PathDeps) android.Path {
+	var checkApiFileTimeStamp android.WritablePath
+
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		if m, ok := dep.(*syspropLibrary); ok {
+			checkApiFileTimeStamp = m.checkApiFileTimeStamp
+		}
+	})
+
+	outputDir := android.PathForModuleOut(ctx, "src")
+	libFile := outputDir.Join(ctx, "lib.rs")
+	g.BaseSourceProvider.OutputFiles = append(g.BaseSourceProvider.OutputFiles, libFile)
+	libFileLines := []string{"//! Autogenerated system property accessors."}
+
+	for _, syspropFile := range android.PathsForModuleSrc(ctx, g.properties.Sysprop_srcs) {
+		moduleName := syspropPathToRustModule(syspropFile)
+		moduleDir := outputDir.Join(ctx, moduleName)
+		modulePath := moduleDir.Join(ctx, "mod.rs")
+
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        syspropRust,
+			Description: "sysprop_rust " + syspropFile.Rel(),
+			Output:      modulePath,
+			Input:       syspropFile,
+			Implicit:    checkApiFileTimeStamp,
+			Args: map[string]string{
+				"scope":   g.properties.Scope,
+				"out_dir": moduleDir.String(),
+			},
+		})
+
+		g.BaseSourceProvider.OutputFiles = append(g.BaseSourceProvider.OutputFiles, modulePath)
+		libFileLines = append(libFileLines, fmt.Sprintf("pub mod %s;", moduleName))
+	}
+
+	libFileSource := strings.Join(libFileLines, "\n")
+	android.WriteFileRule(ctx, libFile, libFileSource)
+
+	return libFile
+}
+
+func (g *syspropRustGenRule) SourceProviderProps() []interface{} {
+	return append(g.BaseSourceProvider.SourceProviderProps(), &g.Properties)
+}
+
+// syspropPathToRustModule takes a path to a .sysprop file and returns the name to use for the
+// corresponding Rust module.
+func syspropPathToRustModule(syspropFilename android.Path) string {
+	filenameBase := strings.TrimSuffix(syspropFilename.Base(), ".sysprop")
+	return strings.ToLower(filenameBase)
+}
+
+func (g *syspropRustGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// Add a dependency from the stubs to sysprop library so that the generator rule can depend on
+	// the check API rule of the sysprop library.
+	ctx.AddFarVariationDependencies(nil, nil, proptools.String(g.properties.Check_api))
+}
+
+func syspropRustGenFactory() android.Module {
+	g := &syspropRustGenRule{
+		BaseSourceProvider: rust.NewSourceProvider(),
+	}
+	sourceProvider := rust.NewSourceProviderModule(android.DeviceSupported, g, false, false)
+	sourceProvider.AddProperties(&g.properties)
+	return sourceProvider.Init()
+}
+
 type syspropLibrary struct {
 	android.ModuleBase
 	android.ApexModuleBase
@@ -180,6 +267,12 @@
 		// Forwarded to java_library.min_sdk_version
 		Min_sdk_version *string
 	}
+
+	Rust struct {
+		// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
+		// Forwarded to rust_library.min_sdk_version
+		Min_sdk_version *string
+	}
 }
 
 var (
@@ -233,6 +326,17 @@
 	return m.BaseModuleName() + "_java_gen_public"
 }
 
+func (m *syspropLibrary) rustGenStubName() string {
+	return "lib" + m.rustCrateName() + "_rust"
+}
+
+func (m *syspropLibrary) rustCrateName() string {
+	moduleName := strings.ToLower(m.BaseModuleName())
+	moduleName = strings.ReplaceAll(moduleName, "-", "_")
+	moduleName = strings.ReplaceAll(moduleName, ".", "_")
+	return moduleName
+}
+
 func (m *syspropLibrary) BaseModuleName() string {
 	return m.ModuleBase.Name()
 }
@@ -436,6 +540,21 @@
 	Min_sdk_version   *string
 }
 
+type rustLibraryProperties struct {
+	Name              *string
+	Sysprop_srcs      []string `android:"path"`
+	Scope             string
+	Check_api         *string
+	Srcs              []string
+	Installable       *bool
+	Crate_name        string
+	Rustlibs          []string
+	Vendor_available  *bool
+	Product_available *bool
+	Apex_available    []string
+	Min_sdk_version   *string
+}
+
 func syspropLibraryHook(ctx android.LoadHookContext, m *syspropLibrary) {
 	if len(m.properties.Srcs) == 0 {
 		ctx.PropertyErrorf("srcs", "sysprop_library must specify srcs")
@@ -564,6 +683,25 @@
 		})
 	}
 
+	// Generate a Rust implementation library.
+	rustProps := rustLibraryProperties{
+		Name:         proptools.StringPtr(m.rustGenStubName()),
+		Sysprop_srcs: m.properties.Srcs,
+		Scope:        scope,
+		Check_api:    proptools.StringPtr(ctx.ModuleName()),
+		Installable:  proptools.BoolPtr(false),
+		Crate_name:   m.rustCrateName(),
+		Rustlibs: []string{
+			"liblog_rust",
+			"librustutils",
+		},
+		Vendor_available:  m.properties.Vendor_available,
+		Product_available: m.properties.Product_available,
+		Apex_available:    m.ApexProperties.Apex_available,
+		Min_sdk_version:   proptools.StringPtr("29"),
+	}
+	ctx.CreateModule(syspropRustGenFactory, &rustProps)
+
 	// syspropLibraries will be used by property_contexts to check types.
 	// Record absolute paths of sysprop_library to prevent soong_namespace problem.
 	if m.ExportedToMake() {
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index e5b3dea..dfbbe7d 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -22,6 +22,7 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/java"
+	"android/soong/rust"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -46,18 +47,6 @@
 			recovery_available: true,
 		}
 
-		cc_library {
-			name: "liblog",
-			no_libcrt: true,
-			nocrt: true,
-			system_shared_libs: [],
-			recovery_available: true,
-			host_supported: true,
-			llndk: {
-				symbol_file: "liblog.map.txt",
-			}
-		}
-
 		java_library {
 			name: "sysprop-library-stub-platform",
 			sdk_version: "core_current",
@@ -74,6 +63,24 @@
 			product_specific: true,
 			sdk_version: "core_current",
 		}
+
+		rust_library {
+			name: "librustutils",
+			crate_name: "rustutils",
+			srcs: ["librustutils/lib.rs"],
+			product_available: true,
+			vendor_available: true,
+			min_sdk_version: "29",
+		}
+
+		rust_library {
+			name: "liblog_rust",
+			crate_name: "log",
+			srcs: ["log/src/lib.rs"],
+			product_available: true,
+			vendor_available: true,
+			min_sdk_version: "29",
+		}
 	`
 
 	mockFS := android.MockFS{
@@ -115,11 +122,15 @@
 		"android/sysprop/PlatformProperties.sysprop": nil,
 		"com/android/VendorProperties.sysprop":       nil,
 		"com/android2/OdmProperties.sysprop":         nil,
+
+		"librustutils/lib.rs": nil,
+		"log/src/lib.rs":      nil,
 	}
 
 	result := android.GroupFixturePreparers(
 		cc.PrepareForTestWithCcDefaultModules,
 		java.PrepareForTestWithJavaDefaultModules,
+		rust.PrepareForTestWithRustDefaultModules,
 		PrepareForTestWithSyspropBuildComponents,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.DeviceSystemSdkVersions = []string{"28"}
@@ -356,6 +367,10 @@
 	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.ApexProperties.Apex_available
 	android.AssertDeepEquals(t, "apex_available forwarding to java module", expected, propFromJava)
+
+	rustModule := result.ModuleForTests("libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
+	propFromRust := rustModule.ApexProperties.Apex_available
+	android.AssertDeepEquals(t, "apex_available forwarding to rust module", expected, propFromRust)
 }
 
 func TestMinSdkVersionIsForwarded(t *testing.T) {
@@ -371,6 +386,9 @@
 			java: {
 				min_sdk_version: "30",
 			},
+			rust: {
+				min_sdk_version: "29",
+			}
 		}
 	`)
 
@@ -381,4 +399,8 @@
 	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.MinSdkVersionString()
 	android.AssertStringEquals(t, "min_sdk_version forwarding to java module", "30", propFromJava)
+
+	rustModule := result.ModuleForTests("libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
+	propFromRust := proptools.String(rustModule.Properties.Min_sdk_version)
+	android.AssertStringEquals(t, "min_sdk_version forwarding to rust module", "29", propFromRust)
 }
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/config.go b/tradefed/config.go
index 326a006..b015034 100644
--- a/tradefed/config.go
+++ b/tradefed/config.go
@@ -33,6 +33,7 @@
 	pctx.SourcePathVariable("NativeTestConfigTemplate", "build/make/core/native_test_config_template.xml")
 	pctx.SourcePathVariable("PythonBinaryHostMoblyTestConfigTemplate", "build/make/core/python_binary_host_mobly_test_config_template.xml")
 	pctx.SourcePathVariable("PythonBinaryHostTestConfigTemplate", "build/make/core/python_binary_host_test_config_template.xml")
+	pctx.SourcePathVariable("RavenwoodTestConfigTemplate", "build/make/core/ravenwood_test_config_template.xml")
 	pctx.SourcePathVariable("RustDeviceTestConfigTemplate", "build/make/core/rust_device_test_config_template.xml")
 	pctx.SourcePathVariable("RustHostTestConfigTemplate", "build/make/core/rust_host_test_config_template.xml")
 	pctx.SourcePathVariable("RustDeviceBenchmarkConfigTemplate", "build/make/core/rust_device_benchmark_config_template.xml")
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/androidmk_denylist.go b/ui/build/androidmk_denylist.go
index e004cdc..e7896ab 100644
--- a/ui/build/androidmk_denylist.go
+++ b/ui/build/androidmk_denylist.go
@@ -16,8 +16,6 @@
 
 import (
 	"strings"
-
-	"android/soong/android"
 )
 
 var androidmk_denylist []string = []string{
@@ -32,16 +30,17 @@
 	"libcore/",
 	"libnativehelper/",
 	"pdk/",
-	"toolchain/",
+	// Add back toolchain/ once defensive Android.mk files are removed
+	//"toolchain/",
 }
 
-func blockAndroidMks(androidMks []string) []string {
-	return android.FilterListPred(androidMks, func(s string) bool {
+func blockAndroidMks(ctx Context, androidMks []string) {
+	for _, mkFile := range androidMks {
 		for _, d := range androidmk_denylist {
-			if strings.HasPrefix(s, d) {
-				return false
+			if strings.HasPrefix(mkFile, d) {
+				ctx.Fatalf("Found blocked Android.mk file: %s. "+
+					"Please see androidmk_denylist.go for the blocked directories and contact build system team if the file should not be blocked.", mkFile)
 			}
 		}
-		return true
-	})
+	}
 }
diff --git a/ui/build/config.go b/ui/build/config.go
index e29d239..7426a78 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
@@ -387,22 +386,21 @@
 
 	// Configure Java-related variables, including adding it to $PATH
 	java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
-	java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
 	java21Home := filepath.Join("prebuilts/jdk/jdk21", ret.HostPrebuiltTag())
 	javaHome := func() string {
 		if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
 			return override
 		}
-		if ret.environ.IsEnvTrue("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
-			return java21Home
-		}
 		if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
-			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
 		}
 		if toolchain17, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN"); ok && toolchain17 != "true" {
-			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 17 toolchain is now the global default.")
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
 		}
-		return java17Home
+		if toolchain21, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN"); ok && toolchain21 != "true" {
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
+		}
+		return java21Home
 	}()
 	absJavaHome := absPath(ctx, javaHome)
 
@@ -424,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 != "" {
@@ -789,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=") {
@@ -1095,10 +1087,6 @@
 	return c.verbose
 }
 
-func (c *configImpl) MultitreeBuild() bool {
-	return c.multitreeBuild
-}
-
 func (c *configImpl) NinjaWeightListSource() NinjaWeightListSource {
 	return c.ninjaWeightListSource
 }
@@ -1398,7 +1386,9 @@
 }
 
 func (c *configImpl) rbeSockAddr(dir string) (string, error) {
-	maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
+	// Absolute path socket addresses have a prefix of //. This should
+	// be included in the length limit.
+	maxNameLen := len(syscall.RawSockaddrUnix{}.Path) - 2
 	base := fmt.Sprintf("reproxy_%v.sock", rbeRandPrefix)
 
 	name := filepath.Join(dir, base)
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/dumpvars.go b/ui/build/dumpvars.go
index d364542..e17bd54 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -237,7 +237,6 @@
 		"BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST",
 
 		// Not used, but useful to be in the soong.log
-		"BOARD_VNDK_VERSION",
 		"TARGET_BUILD_TYPE",
 		"HOST_ARCH",
 		"HOST_2ND_ARCH",
diff --git a/ui/build/finder.go b/ui/build/finder.go
index a114079..573df21 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -128,7 +128,7 @@
 
 	// Stop searching a subdirectory recursively after finding an Android.mk.
 	androidMks := f.FindFirstNamedAt(".", "Android.mk")
-	androidMks = blockAndroidMks(androidMks)
+	blockAndroidMks(ctx, androidMks)
 	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
 	if err != nil {
 		ctx.Fatalf("Could not export module list: %v", err)
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index fa04207..5142a41 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -163,7 +163,7 @@
 		return
 	}
 	fmt.Fprintln(ctx.Writer, "")
-	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This will result in failing builds in the future, see go/rbe-android-default-announcement.\033[0m")
+	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This is required for a successful build execution. See go/rbe-android-default-announcement for more information.\033[0m")
 	fmt.Fprintln(ctx.Writer, "")
 }
 
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")
 	}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index f4e3fb8..8c3ff29 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -206,10 +206,11 @@
 		}
 		if msg.EdgeStarted != nil {
 			action := &Action{
-				Description: msg.EdgeStarted.GetDesc(),
-				Outputs:     msg.EdgeStarted.Outputs,
-				Inputs:      msg.EdgeStarted.Inputs,
-				Command:     msg.EdgeStarted.GetCommand(),
+				Description:   msg.EdgeStarted.GetDesc(),
+				Outputs:       msg.EdgeStarted.Outputs,
+				Inputs:        msg.EdgeStarted.Inputs,
+				Command:       msg.EdgeStarted.GetCommand(),
+				ChangedInputs: msg.EdgeStarted.ChangedInputs,
 			}
 			n.status.StartAction(action)
 			running[msg.EdgeStarted.GetId()] = action
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
index d8344c8..fce7ca2 100644
--- a/ui/status/ninja_frontend/frontend.pb.go
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -363,6 +363,8 @@
 	Command *string `protobuf:"bytes,6,opt,name=command" json:"command,omitempty"`
 	// Edge uses console.
 	Console *bool `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
+	// Changed inputs.
+	ChangedInputs []string `protobuf:"bytes,8,rep,name=changed_inputs,json=changedInputs" json:"changed_inputs,omitempty"`
 }
 
 func (x *Status_EdgeStarted) Reset() {
@@ -446,6 +448,13 @@
 	return false
 }
 
+func (x *Status_EdgeStarted) GetChangedInputs() []string {
+	if x != nil {
+		return x.ChangedInputs
+	}
+	return nil
+}
+
 type Status_EdgeFinished struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -678,7 +687,7 @@
 
 var file_frontend_proto_rawDesc = []byte{
 	0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xa9, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
+	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xdb, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
 	0x75, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65,
 	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e,
 	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
@@ -717,7 +726,7 @@
 	0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x65, 0x73, 0x74, 0x69,
 	0x6d, 0x61, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x0f,
 	0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x1a,
-	0xb6, 0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
+	0xe8, 0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
 	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12,
 	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
 	0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16,
@@ -728,50 +737,54 @@
 	0x64, 0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18,
 	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18,
 	0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45, 0x64, 0x67,
-	0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64,
-	0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65, 0x6e, 0x64,
-	0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06,
-	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d,
-	0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d,
-	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69,
-	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62,
-	0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66,
-	0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e,
-	0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11,
-	0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74,
-	0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61,
-	0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69,
-	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69,
-	0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
-	0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f,
-	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
-	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18,
-	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
-	0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f,
-	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
-	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a,
-	0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
-	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61,
-	0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x92,
-	0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65,
-	0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a,
-	0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05, 0x6c, 0x65,
-	0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a,
-	0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00,
-	0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a,
-	0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55,
-	0x47, 0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x2f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64,
+	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x68, 0x61, 0x6e,
+	0x67, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73,
+	0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x53,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45,
+	0x64, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65,
+	0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65,
+	0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16,
+	0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74,
+	0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54,
+	0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69,
+	0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f,
+	0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73,
+	0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65,
+	0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d,
+	0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a,
+	0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75,
+	0x6c, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72,
+	0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f,
+	0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f,
+	0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a,
+	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
+	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e,
+	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e,
+	0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04,
+	0x74, 0x61, 0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73,
+	0x1a, 0x92, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05,
+	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69,
+	0x6e, 0x6a, 0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05,
+	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
+	0x34, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f,
+	0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12,
+	0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45,
+	0x42, 0x55, 0x47, 0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x2f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+	0x64,
 }
 
 var (
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
index 42b251b..faa0d34 100644
--- a/ui/status/ninja_frontend/frontend.proto
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -54,6 +54,8 @@
     optional string command = 6;
     // Edge uses console.
     optional bool console = 7;
+    // Changed inputs since last build.
+    repeated string changed_inputs = 8;
   }
 
   message EdgeFinished {
diff --git a/ui/status/status.go b/ui/status/status.go
index da78994..52ed56a 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -41,6 +41,10 @@
 	// It's optional, but one of either Description or Command should be
 	// set.
 	Command string
+
+	// ChangedInputs is the (optional) list of inputs that have changed
+	// since last time this action was run.
+	ChangedInputs []string
 }
 
 // ActionResult describes the result of running an Action.
diff --git a/ui/tracer/status.go b/ui/tracer/status.go
index f973613..8acf561 100644
--- a/ui/tracer/status.go
+++ b/ui/tracer/status.go
@@ -15,9 +15,10 @@
 package tracer
 
 import (
-	"android/soong/ui/status"
 	"strings"
 	"time"
+
+	"android/soong/ui/status"
 )
 
 func (t *tracerImpl) StatusTracer() status.StatusOutput {
@@ -110,6 +111,7 @@
 			VoluntaryContextSwitches:   result.Stats.VoluntaryContextSwitches,
 			InvoluntaryContextSwitches: result.Stats.InvoluntaryContextSwitches,
 			Tags:                       s.parseTags(result.Stats.Tags),
+			ChangedInputs:              result.Action.ChangedInputs,
 		},
 	})
 }
@@ -125,6 +127,7 @@
 	VoluntaryContextSwitches   uint64            `json:"voluntary_context_switches"`
 	InvoluntaryContextSwitches uint64            `json:"involuntary_context_switches"`
 	Tags                       map[string]string `json:"tags"`
+	ChangedInputs              []string          `json:"changed_inputs"`
 }
 
 func (s *statusOutput) Flush()                                        {}